commit
7b6f007258
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ dist/
|
||||
examples/node_modules
|
||||
examples/package-lock.json
|
||||
examples/package.json
|
||||
docs/
|
||||
@ -1,181 +0,0 @@
|
||||
# Deezer
|
||||
|
||||
## Main
|
||||
|
||||
### deezer(url : `string`)
|
||||
|
||||
_This returns data from a track | playlist | album url. Accepts share links as well, which it resolves first._
|
||||
|
||||
```js
|
||||
let data = await deezer(url); //Gets the data
|
||||
|
||||
console.log(data.type); // Console logs the type of data that you got.
|
||||
```
|
||||
|
||||
## Validate
|
||||
|
||||
### dz_validate(url : `string`)
|
||||
|
||||
_This checks that given url is Deezer url or not._
|
||||
|
||||
**Returns :** `track` | `album` | `playlist` | `search` | `false`
|
||||
|
||||
```js
|
||||
let check = dz_validate(url)
|
||||
|
||||
if(!check) // Invalid Deezer URL
|
||||
|
||||
if(check === 'track') // Deezer Track URL
|
||||
|
||||
if(check === "search") // Given term is a search query. Search it somewhere.
|
||||
```
|
||||
|
||||
## Search
|
||||
|
||||
### dz_search(query: `string`, options: `DeezerSearchOptions`)
|
||||
|
||||
_Searches for tracks, playlists and albums._
|
||||
|
||||
**Returns :** `Deezer[]` an array of tracks, playlists or albums
|
||||
|
||||
#### `DeezerSearchOptions`
|
||||
|
||||
- **type?** `'track'` | `'playlist'` | `'album'` The type to search for. Defaults to `'track'`.
|
||||
- **limit?** `number` The maximum number of results to return. Maximum `100`, defaults to `10`.
|
||||
- **fuzzy?** `boolean` Whether the search should be fuzzy or only return exact matches. Defaults to `true`.
|
||||
|
||||
```js
|
||||
const results = await dz_search(query, {
|
||||
limit: 1,
|
||||
type: 'track',
|
||||
fuzzy: false
|
||||
}); // Returns an array with one track, using exact matching
|
||||
```
|
||||
|
||||
### dz_advanced_track_search(options: `DeezerAdvancedSearchOptions`)
|
||||
|
||||
_Searches Deezer for tracks using the specified metadata._
|
||||
|
||||
**Returns :** `DeezerTrack[]` an array of tracks
|
||||
|
||||
#### `DeezerAdvancedSearchOptions`
|
||||
|
||||
- **limit?** `number` The maximum number of results to return, maximum `100`, defaults to `10`.
|
||||
- **artist?** `string` The name of the artist
|
||||
- **album?** `string` The title of the album
|
||||
- **title?** `string` The title of the track
|
||||
- **label?** `string` The label that released the track
|
||||
- **minDurationInSec?** `number` The minimum duration in seconds
|
||||
- **maxDurationInSec?** `number` The maximum duration in seconds
|
||||
- **minBpm?** `number` The minimum BPM
|
||||
- **maxBpm?** `number` The minimum BPM
|
||||
|
||||
```js
|
||||
const results = await dz_advanced_track_search({
|
||||
limit: 1,
|
||||
artist: 'Rick Astley',
|
||||
title: 'Never Gonna Give You Up'
|
||||
}); // Returns an array with one track
|
||||
```
|
||||
|
||||
## Classes [ Returned by `deezer(url)` function ]
|
||||
|
||||
### DeezerTrack
|
||||
|
||||
_This is the class for a Deezer track._
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "track" for this class._
|
||||
|
||||
##### partial `property`
|
||||
|
||||
_Will return true for tracks in search results and false for tracks fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_Converts the object to JSON_
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_Fetches the missing data for a partial track._
|
||||
|
||||
```js
|
||||
const track = await deezer(track_url);
|
||||
|
||||
await track.fetch() // Fetches the missing data
|
||||
```
|
||||
|
||||
### DeezerPlaylist
|
||||
|
||||
_This is the class for a Deezer playlist._
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_This will fetch up to 1000 tracks in a playlist as well as the missing data for a partial playlist._
|
||||
|
||||
```js
|
||||
let data = await deezer(playlist_url)
|
||||
|
||||
await data.fetch() // Fetches tracks more than 100 tracks in playlist
|
||||
```
|
||||
|
||||
##### tracksCount `property`
|
||||
|
||||
_This will return the total number of tracks in a playlist._
|
||||
|
||||
```js
|
||||
const data = await deezer(playlist_url)
|
||||
|
||||
console.log(data.tracksCount) // Total number of tracks in the playlist.
|
||||
```
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "playlist" for this class._
|
||||
|
||||
##### partial `property`
|
||||
|
||||
_Will return true for playlists in search results and false for playlists fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined and `tracks` may be empty or partially filled._
|
||||
|
||||
##### tracks `property`
|
||||
|
||||
_The array of tracks in this album, this is always empty (length of 0) for partial playlists._
|
||||
|
||||
```js
|
||||
const data = await deezer(playlist_url);
|
||||
|
||||
if (data.tracks.length !== data.tracksCount) {
|
||||
await data.fetch();
|
||||
}
|
||||
|
||||
console.log(data.tracks); // returns all tracks in the playlist
|
||||
```
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_Converts the object to JSON_
|
||||
|
||||
### DeezerAlbum
|
||||
|
||||
_This is the class for a Deezer album._
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "track" for this class._
|
||||
|
||||
##### tracks `property`
|
||||
|
||||
_The array of tracks in this album, this is always empty (length of 0) for partial albums._
|
||||
|
||||
##### partial `property`
|
||||
|
||||
_Will return true for albums in search results and false for albums fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_Converts the object to JSON_
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_Fetches the missing data for a partial album._
|
||||
190
docs/README.md
190
docs/README.md
@ -1,190 +0,0 @@
|
||||
# Play-dl commands
|
||||
|
||||
For source specific commands :-
|
||||
|
||||
- [YouTube](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#youtube)
|
||||
- [Spotify](https://github.com/play-dl/play-dl/tree/main/docs/Spotify#spotify)
|
||||
- [SoundCloud](https://github.com/play-dl/play-dl/tree/main/docs/SoundCloud)
|
||||
|
||||
### Validate
|
||||
|
||||
#### validate(url : `string`)
|
||||
|
||||
_This checks all type of urls that are supported by play-dl._
|
||||
|
||||
**Returns :** `so_playlist` | `so_track` | `sp_track` | `sp_album` | `sp_playlist` | `dz_track` | `dz_playlist` | `dz_album` | `yt_video` | `yt_playlist` | `search` | `false`
|
||||
|
||||
`so` = **SoundCloud**
|
||||
|
||||
`sp` = **Spotify**
|
||||
|
||||
`yt` = **YouTube**
|
||||
|
||||
`dz` = **Deezer**
|
||||
|
||||
```js
|
||||
let check = await validate(url)
|
||||
|
||||
if(!check) // Invalid URL
|
||||
|
||||
if(check === 'yt_video') // YouTube Video
|
||||
|
||||
if(check === 'sp_track') // Spotify Track
|
||||
|
||||
if(check === 'so_track') // SoundCloud Track
|
||||
|
||||
if(check === 'dz_track') // Deezer Track
|
||||
|
||||
if(check === "search") // Given term is not a url. Search this term somewhere.
|
||||
```
|
||||
|
||||
### authorization()
|
||||
|
||||
_This creates basic spotify / soundcloud / youtube data to be stored locally._
|
||||
|
||||
```js
|
||||
authorization() //After then you will be asked about type of data you want to create and then follow the steps properly.
|
||||
```
|
||||
|
||||
### setToken(options : `TokenOptions`)
|
||||
|
||||
_This sets token without using file._
|
||||
|
||||
```js
|
||||
setToken({
|
||||
spotify : {
|
||||
client_id : "ID",
|
||||
client_secret : "Secret",
|
||||
refresh_token : "Token",
|
||||
market : "Country Code"
|
||||
}
|
||||
}) // Setting Spotify Token [ To get refresh_token, just run through authorization, and set file save to No ]
|
||||
|
||||
setToken({
|
||||
soundcloud : {
|
||||
client_id : "ID"
|
||||
}
|
||||
}) // Setting SoundCloud Token
|
||||
|
||||
setToken({
|
||||
youtube : {
|
||||
cookie : "Cookies"
|
||||
}
|
||||
}) // Warning : Using setToken for youtube cookies will only update cookies present in memory only.
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
#### SearchOptions :
|
||||
|
||||
- limit : `number` :- Sets total amount of results you want.
|
||||
- source : {
|
||||
|
||||
youtube: `video` | `playlist` | `channel` ;
|
||||
|
||||
spotify: `album` | `playlist` | `track` ;
|
||||
|
||||
soundcloud: `tracks` | `playlists` | `albums` ;
|
||||
|
||||
deezer: `track` | `playlist` | `album` ;
|
||||
|
||||
}
|
||||
|
||||
#### search(query : `string`, options? : [`SearchOptions`](https://github.com/play-dl/play-dl/tree/main/docs#searchoptions-))
|
||||
|
||||
_This is basic to search with any source._
|
||||
|
||||
**NOTE :-** If options.source is not specified, then it will default to youtube video search.
|
||||
|
||||
```js
|
||||
let data = await search('Rick Roll', { limit : 1 }) // Searches for youtube video
|
||||
|
||||
let data = await search('Rick Roll', { limit : 1, source : { youtube : "video" } }) // Searches for youtube video
|
||||
|
||||
let data = await search('Rick Roll', { limit: 1, source : { spotify : "track" } }) // Searches for spotify track.
|
||||
|
||||
let data = await search('Rick Roll', { limit: 1, source : { soundcloud : "tracks" } }) // Searches for soundcloud track.
|
||||
|
||||
let data = await search('Rick Roll', { limit: 1, source : { deezer : "track" } }) // Searches for a Deezer track.
|
||||
```
|
||||
|
||||
### Stream
|
||||
|
||||
**Attaching events to player is important for stream to work.**
|
||||
|
||||
#### attachListeners(player : `AudioPlayer`, resource : `YouTubeStream | SoundCloudStream`)
|
||||
|
||||
_This is used for attaching pause and playing events to audioPlayer._
|
||||
|
||||
```js
|
||||
let resource = await stream("url")
|
||||
|
||||
let player = createAudioPlayer()
|
||||
|
||||
attachListeners(player, resource)
|
||||
```
|
||||
|
||||
#### StreamOptions :
|
||||
|
||||
- quality : `number` :- Sets quality of stream [ 0 = Lowest, 1 = Medium ]. Leave this empty to get highest audio quality.
|
||||
- proxy : `Proxy` :- Optional parameter to add support of proxies. As of now, HTTPS proxies are only supported. So make sure to get HTTPS proxies only.
|
||||
|
||||
#### stream(url : `string`, options? : [`StreamOptions`](https://github.com/play-dl/play-dl/tree/main/docs#streamoptions-))
|
||||
|
||||
_This is basic to create a stream from a youtube or soundcloud url._
|
||||
|
||||
```js
|
||||
let source = await stream("url") // This will create a stream Class. Highest Quality
|
||||
|
||||
let source = await stream("url", { quality : 0 }) // Lowest quality
|
||||
|
||||
let source = await stream("url", { quality : 1 }) // Next to Lowest quality.
|
||||
|
||||
let source = await stream(url, { proxy : ['url'] }) // Accepts a url which has port in it.
|
||||
|
||||
let source = await stream(url. {proxy : [{
|
||||
host : "IP or hostname",
|
||||
port : 8080
|
||||
}]
|
||||
}) // Or add a json containing hostname and port.
|
||||
|
||||
let resource = createAudioResource(source.stream, {
|
||||
inputType : source.type
|
||||
}) // This creates resource for playing
|
||||
```
|
||||
|
||||
#### stream_from_info(info : `infoData`, options? : [`StreamOptions`](https://github.com/play-dl/play-dl/tree/main/docs#streamoptions-))
|
||||
|
||||
_This is basic to create a stream from a info [ from [video_info](https://github.com/play-dl/play-dl#video_infourl--string) function or [soundcloud](https://github.com/play-dl/play-dl/tree/main/docs/SoundCloud#soundcloudurl--string) function [**Only SoundCloudTrack class is allowed**] ]._
|
||||
|
||||
**Note :** Here, cookies are required only for retrying purposes.
|
||||
|
||||
```js
|
||||
let source = await stream_from_info(info) // This will create a stream Class from video_info or SoundCoudTrack Class. Highest Quality
|
||||
|
||||
let source = await stream_from_info(info, { quality : 0 }) // Lowest quality
|
||||
|
||||
let source = await stream_from_info(info, { quality : 1 }) // Next to Lowest quality.
|
||||
|
||||
let source = await stream_from_info(info, { proxy : ['url'] }) // Accepts a url which has port in it.
|
||||
|
||||
let source = await stream_from_info(info, {proxy : [{
|
||||
host : "IP or hostname",
|
||||
port : 8080
|
||||
}]
|
||||
}) // Or add a json containing hostname and port.
|
||||
|
||||
let resource = createAudioResource(source.stream, {
|
||||
inputType : source.type
|
||||
}) // This creates resource for playing
|
||||
```
|
||||
|
||||
#### cookieHeaders(headersCookie : `string[]`)
|
||||
|
||||
_This is function to update youtube cookies when using external https module._
|
||||
|
||||
```js
|
||||
const res = ... // You need to get response.
|
||||
|
||||
play.cookieHeaders(res.headers['set-cookie']) // Updates YouTube Cookies if cookies exists.
|
||||
```
|
||||
@ -1,115 +0,0 @@
|
||||
# SoundCloud
|
||||
|
||||
## Main
|
||||
|
||||
### soundcloud(url : `string`)
|
||||
|
||||
_This returns data from a track | playlist url._
|
||||
|
||||
```js
|
||||
let data = await soundcloud(url) //Gets the data
|
||||
|
||||
console.log(data.type) // Console logs the type of data that you got.
|
||||
```
|
||||
|
||||
### getFreeClientID()
|
||||
|
||||
_This returns free client ID._
|
||||
|
||||
```js
|
||||
const client_id = await getFreeClientID()
|
||||
|
||||
setToken({
|
||||
soundcloud : {
|
||||
client_id : client_id
|
||||
}
|
||||
}) // This will set client ID for use in play-dl.
|
||||
```
|
||||
|
||||
## Validate
|
||||
|
||||
### so_validate(url : `string`)
|
||||
|
||||
_This checks that given url is soundcloud url or not._
|
||||
|
||||
**Returns :** `track` | `playlist` | `search` | `false`
|
||||
|
||||
```js
|
||||
let check = await so_validate(url)
|
||||
|
||||
if(!check) // Invalid SoundCloud URL
|
||||
|
||||
if(check === 'track') // SoundCloud Track URL
|
||||
|
||||
if(check === "search") // Given term is not a SoundCloud URL. Search this somewhere.
|
||||
```
|
||||
|
||||
## Classes [ Returned by `soundcloud(url)` function ]
|
||||
|
||||
### SoundCloudTrack
|
||||
|
||||
_This is class for a soundcloud track._
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "track" for this class._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_converts class into a json format_
|
||||
|
||||
### SoundCloudPlaylist
|
||||
|
||||
_This is a soundcloud playlist class._
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_This will fetch tracks in a playlist._
|
||||
|
||||
```js
|
||||
let data = await soundcloud(playlist_url)
|
||||
|
||||
await data.fetch() // Fetches all unfetched tracks in playlist
|
||||
```
|
||||
|
||||
##### tracksCount `property`
|
||||
|
||||
_This will give no. of tracks in a playlist._
|
||||
|
||||
```js
|
||||
let data = await soundcloud(playlist_url)
|
||||
|
||||
console.log(data.tracksCount) // Returns total tracks count in a playlist
|
||||
```
|
||||
|
||||
#### tracks `property`
|
||||
|
||||
_This will give all tracks fetched as array._
|
||||
|
||||
```js
|
||||
let data = await soundcloud(playlist_url)
|
||||
|
||||
console.log(data.tracks) // Tracks Array
|
||||
|
||||
data.tracks.forEach((track) => {
|
||||
queue.push(track) // This will push every track in playlist to your queue
|
||||
})
|
||||
```
|
||||
|
||||
#### total_tracks `property`
|
||||
|
||||
_This give total videos that have been fetched so far._
|
||||
|
||||
```js
|
||||
let data = await soundcloud(playlist_url)
|
||||
|
||||
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
|
||||
```
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "playlist" for this class._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_converts class into a json format_
|
||||
@ -1,204 +0,0 @@
|
||||
# Spotify
|
||||
|
||||
## Main
|
||||
|
||||
### spotify(url : `string`)
|
||||
|
||||
_This returns data from a track | playlist | album url._
|
||||
|
||||
```js
|
||||
let data = await spotify(url) //Gets the data
|
||||
|
||||
console.log(data.type) // Console logs the type of data that you got.
|
||||
```
|
||||
|
||||
|
||||
## Validate
|
||||
|
||||
### sp_validate(url : `string`)
|
||||
|
||||
_This checks that given url is spotify url or not._
|
||||
|
||||
**Returns :** `track` | `album` | `playlist` | `search` | `false`
|
||||
|
||||
```js
|
||||
let check = sp_validate(url)
|
||||
|
||||
if(!check) // Invalid Spotify URL
|
||||
|
||||
if(check === 'track') // Spotify Track URL
|
||||
|
||||
if(check === "search") // Given term is a spotify url. Search it somewhere.
|
||||
```
|
||||
|
||||
### is_expired()
|
||||
|
||||
_This tells that whether the access token is expired or not_
|
||||
|
||||
**Returns :** `boolean`
|
||||
|
||||
```js
|
||||
if(is_expired()){
|
||||
await refreshToken()
|
||||
}
|
||||
```
|
||||
|
||||
### refreshToken()
|
||||
|
||||
_This refreshes the access token._
|
||||
|
||||
**Returns :** `boolean` for telling whether access token is refreshed or not
|
||||
|
||||
```js
|
||||
await refreshToken()
|
||||
```
|
||||
|
||||
## Classes [ Returned by `spotify(url)` function ]
|
||||
|
||||
### SpotifyVideo
|
||||
|
||||
_Don't go by the name. This is class for a spotify track._
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "track" for this class._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_converts class into a json format_
|
||||
|
||||
### SpotifyPlaylist
|
||||
|
||||
_This is a spotify playlist class._
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_This will fetch tracks in a playlist upto 1000 tracks only._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
await data.fetch() // Fetches tracks more than 100 tracks in playlist
|
||||
```
|
||||
|
||||
##### tracksCount `property`
|
||||
|
||||
_This will give no. of tracks in a playlist._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.tracksCount) // Returns total tracks count in a playlist
|
||||
```
|
||||
|
||||
##### page(page_number : `number`)
|
||||
|
||||
_This will return array of tracks in that page._
|
||||
|
||||
> Same as youtube playlist pages
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.page(1)) //This will give first 100 tracks in playlist.
|
||||
```
|
||||
|
||||
- total_pages `property`
|
||||
|
||||
_This give total pages that have been fetched so far._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.total_pages) // This will tell no. of pages that have been fetched so far.
|
||||
|
||||
for(let i = 1; i <= data.total_pages; i++){
|
||||
queue.push(data.page(i)) //This will push all tracks to your queue system
|
||||
}
|
||||
```
|
||||
|
||||
- total_tracks `property`
|
||||
|
||||
_This give total videos that have been fetched so far._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
|
||||
```
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "playlist" for this class._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_converts class into a json format_
|
||||
|
||||
### SpotifyAlbum
|
||||
|
||||
_This is a spotify albun class._
|
||||
|
||||
##### fetch() `function`
|
||||
|
||||
_This will fetch tracks in a album upto 500 tracks only._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
await data.fetch() // Fetches tracks more than 50 tracks in album
|
||||
```
|
||||
|
||||
##### tracksCount `property`
|
||||
|
||||
_This will give no. of tracks in a playlist._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.tracksCount) // Returns total tracks count in a album
|
||||
```
|
||||
|
||||
##### page(page_number : `number`)
|
||||
|
||||
_This will return array of tracks in that page._
|
||||
|
||||
> Same as youtube playlist pages
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.page(1)) //This will give first 50 tracks in album.
|
||||
```
|
||||
|
||||
- total_pages `property`
|
||||
|
||||
_This give total pages that have been fetched so far._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.total_pages) // This will tell no. of pages that have been fetched so far.
|
||||
|
||||
for(let i = 1; i <= data.total_pages; i++){
|
||||
queue.push(data.page(i)) //This will push all tracks to your queue system
|
||||
}
|
||||
```
|
||||
|
||||
- total_tracks `property`
|
||||
|
||||
_This give total videos that have been fetched so far._
|
||||
|
||||
```js
|
||||
let data = await spotify(playlist_url)
|
||||
|
||||
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
|
||||
```
|
||||
|
||||
##### type `property`
|
||||
|
||||
_This will always return as "album" for this class._
|
||||
|
||||
##### toJSON() `function`
|
||||
|
||||
_converts class into a json format_
|
||||
@ -1,198 +0,0 @@
|
||||
# YouTube
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```js
|
||||
const youtube = require('play-dl');
|
||||
// ES6: import youtube from 'play-dl';
|
||||
const options = {
|
||||
limit : 1
|
||||
}
|
||||
const results = await youtube.search('post malone sunflower', options);
|
||||
```
|
||||
|
||||
## Validate
|
||||
|
||||
### yt_validate(url : `string`)
|
||||
|
||||
_This will validate url and return type or boolean_
|
||||
|
||||
**Returns :** `video` | `playlist` | `search` | `false`
|
||||
|
||||
```js
|
||||
let check = yt_validate(url)
|
||||
|
||||
if(!check) // Invalid URL
|
||||
|
||||
if(check === "video") //URL is video url
|
||||
|
||||
if(check === "playlist") //URL is a playlist url
|
||||
|
||||
if(check === "search") // Given term is not a video ID and PlayList ID.
|
||||
```
|
||||
|
||||
## Extract ID
|
||||
|
||||
### extractID(url : `string`)
|
||||
|
||||
_This will return videoID or playlistID from a url_
|
||||
|
||||
**Note :** URL like [this](https://www.youtube.com/watch?v=E2gHczUOCGI&list=PLUt3leKZfbZqLzLwcQMYPBdbe7i7KRCOP&index=2) will return a playlist ID only.
|
||||
|
||||
```js
|
||||
let id = extractID(url)
|
||||
```
|
||||
|
||||
## Video
|
||||
|
||||
### InfoOptions
|
||||
|
||||
_This are the info options that can be passed as a parameter in `video_info` and `video_basic_info`_
|
||||
|
||||
- proxy : Optional parameter to add support of proxies. As of now, HTTPS proxies are only supported. So make sure to get HTTPS proxies only.
|
||||
- htmldata : `boolean` Set this to true if you are passing a html body as first parameter.
|
||||
|
||||
```js
|
||||
const video = await video_basic_info(url, { proxy : ['url'] }) // Accepts a url which has port in it.
|
||||
|
||||
const video = await video_basic_info(url, {proxy : [{
|
||||
host : "IP or hostname",
|
||||
port : 8080
|
||||
}]
|
||||
}) // Or add a json containing hostname and port.
|
||||
|
||||
// Use any https package to use proxy and then do this
|
||||
|
||||
const video = await video_basic_info(body, { htmldata : true }) // You can use video_info function also.
|
||||
```
|
||||
|
||||
### video_basic_info(url : `string`, options? : [`InfoOptions`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#infooptions))
|
||||
|
||||
_The basic video details `play-dl` fetches at first from url or videoID._
|
||||
|
||||
```js
|
||||
const video = await video_basic_info(url)
|
||||
```
|
||||
|
||||
### video_info(url : `string`, , options? : [`InfoOptions`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#infooptions))
|
||||
|
||||
_This contains everything with deciphered formats along with `video_details`. It can fetech data from url or videoID._
|
||||
|
||||
```js
|
||||
const video = await video_info(url)
|
||||
```
|
||||
|
||||
- #### format `property`
|
||||
|
||||
_This returns all the formats available for a video._
|
||||
|
||||
```js
|
||||
const video = await video_info(url)
|
||||
console.log(video.format)
|
||||
```
|
||||
|
||||
### decipher_info(data : `InfoData`)
|
||||
|
||||
_This contains everything with deciphered formats along with `video_details`. It uses data returned by [`video_basic_info`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#video_basic_infourl--string-options--infooptions). This function is useful if you use [`video_basic_info`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#video_basic_infourl--string-options--infooptions) earlier in your code and want to convert the output for use with [`stream_from_info`](https://github.com/play-dl/play-dl/tree/main/docs#stream_from_infoinfo--infodata-options--streamoptions)_
|
||||
|
||||
```js
|
||||
const basic_video = await video_basic_info(url);
|
||||
|
||||
const video = await decipher_info(basic_video);
|
||||
```
|
||||
|
||||
## Playlist
|
||||
|
||||
### playlist_info(url : `string`, options : `PlaylistOptions`)
|
||||
|
||||
_This fetches all details about a playlist from a url or playlistID._
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url)
|
||||
//This only fetches first 100 videos from a playlist
|
||||
|
||||
const playlist = await playlist_info(url, { incomplete : true })
|
||||
//This only fetches first 100 videos from a playlist and also parses playlist with hidden videos
|
||||
|
||||
const playlist = await playlist_info(url, { proxy : [''] }) // Same 2 options as mentioned in InfoOptions
|
||||
```
|
||||
|
||||
- #### fetch() `method`
|
||||
|
||||
_This fetches and returns all videos from the whole provided playlist ._
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url)
|
||||
//This only fetches first 100 videos from a playlist
|
||||
|
||||
await playlist.fetch()
|
||||
// This one fetches all videos from a playlist.
|
||||
```
|
||||
|
||||
- #### page(page_number : `number`)
|
||||
|
||||
_This returns no. of videos from a page._
|
||||
|
||||
> Every 100 videos have been divided into pages.
|
||||
> Example: There are 782 videos in a playlist, so there will be 8 pages.
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url);
|
||||
// This only fetches first 100 videos from a playlist.
|
||||
|
||||
await playlist.fetch();
|
||||
// This one fetches all videos from a playlist.
|
||||
|
||||
console.log(playlist.page(1));
|
||||
// This displays first 100 videos of a playlist
|
||||
|
||||
```
|
||||
|
||||
- #### total_pages `property`
|
||||
|
||||
_This returns total no. of pages that have been fetched so far._
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url)
|
||||
//This only fetches first 100 videos from a playlist.
|
||||
|
||||
await playlist.fetch()
|
||||
// This one fetches all videos from a playlist.
|
||||
|
||||
console.log(playlist.total_pages)
|
||||
// This displays total no. of pages fetched so far.
|
||||
|
||||
for(let i = 1; i <= playlist.total_pages; i++){
|
||||
queue.push(...playlist.page(i))
|
||||
} // This will push every video in that playlist to your queue
|
||||
```
|
||||
|
||||
- #### total_videos `property`
|
||||
|
||||
_This returns total no. of videos that have been fetched so far._
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url)
|
||||
//This only fetches first 100 videos from a playlist.
|
||||
|
||||
await playlist.fetch()
|
||||
// This one fetches all videos from a playlist.
|
||||
|
||||
console.log(playlist.total_videos)
|
||||
// This displays total no. of videos fetched so far.
|
||||
```
|
||||
|
||||
- #### videoCount `property`
|
||||
|
||||
_This returns total no. of videos in the provided playlist._
|
||||
|
||||
```js
|
||||
const playlist = await playlist_info(url)
|
||||
//This only fetches first 100 videos from a playlist.
|
||||
|
||||
await playlist.fetch()
|
||||
// This one fetches all videos from a playlist.
|
||||
|
||||
console.log(playlist.videoCount)
|
||||
// This displays total no. of videos in a playlist.
|
||||
```
|
||||
369
package-lock.json
generated
369
package-lock.json
generated
@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "play-dl",
|
||||
"version": "1.2.6",
|
||||
"version": "1.3.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "play-dl",
|
||||
"version": "1.2.6",
|
||||
"version": "1.3.0",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.4",
|
||||
"prettier": "^2.3.1",
|
||||
"typedoc": "^0.22.9",
|
||||
"typedoc-plugin-missing-exports": "^0.22.4",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -23,6 +25,142 @@
|
||||
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
|
||||
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"marked": "bin/marked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/onigasm": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz",
|
||||
"integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
|
||||
@ -35,6 +173,48 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "0.9.12",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.12.tgz",
|
||||
"integrity": "sha512-VXcROdldv0/Qu0w2XvzU4IrvTeBNs/Kj/FCmtcEXGz7Tic/veQzliJj6tEiAgoKianhQstpYmbPDStHU5Opqcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"onigasm": "^2.2.5",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.22.9",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
|
||||
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.2.0",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^3.0.8",
|
||||
"minimatch": "^3.0.4",
|
||||
"shiki": "^0.9.12"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-missing-exports": {
|
||||
"version": "0.22.4",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-0.22.4.tgz",
|
||||
"integrity": "sha512-NRagPFyh8Oma5E7rR8534x4d8GQ/87Tp3WYfBJcxradPjXTZ4D2MXD5eHfG98DQCMqYrb+CRZnvO4iaGaqQC7w==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"typedoc": "0.22.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
@ -47,6 +227,24 @@
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -56,17 +254,184 @@
|
||||
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"marked": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
|
||||
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"onigasm": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz",
|
||||
"integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
|
||||
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
|
||||
"dev": true
|
||||
},
|
||||
"shiki": {
|
||||
"version": "0.9.12",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.12.tgz",
|
||||
"integrity": "sha512-VXcROdldv0/Qu0w2XvzU4IrvTeBNs/Kj/FCmtcEXGz7Tic/veQzliJj6tEiAgoKianhQstpYmbPDStHU5Opqcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"onigasm": "^2.2.5",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.22.9",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
|
||||
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.2.0",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^3.0.8",
|
||||
"minimatch": "^3.0.4",
|
||||
"shiki": "^0.9.12"
|
||||
}
|
||||
},
|
||||
"typedoc-plugin-missing-exports": {
|
||||
"version": "0.22.4",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-0.22.4.tgz",
|
||||
"integrity": "sha512-NRagPFyh8Oma5E7rR8534x4d8GQ/87Tp3WYfBJcxradPjXTZ4D2MXD5eHfG98DQCMqYrb+CRZnvO4iaGaqQC7w==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
||||
"dev": true
|
||||
},
|
||||
"vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,8 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.4",
|
||||
"prettier": "^2.3.1",
|
||||
"typedoc": "^0.22.9",
|
||||
"typedoc-plugin-missing-exports": "^0.22.4",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,3 +255,5 @@ export async function dz_advanced_track_search(options: DeezerAdvancedSearchOpti
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export { DeezerTrack, DeezerAlbum, DeezerPlaylist }
|
||||
@ -3,48 +3,130 @@ import { Readable } from 'node:stream';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { StreamType } from '../YouTube/stream';
|
||||
import { Timer } from '../YouTube/classes/LiveStream';
|
||||
import { PlaylistJSON, SoundTrackJSON } from './constants';
|
||||
|
||||
interface SoundCloudUser {
|
||||
export interface SoundCloudUser {
|
||||
/**
|
||||
* SoundCloud User Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* SoundCloud User ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* SoundCloud User URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Class type. == "user"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'user';
|
||||
/**
|
||||
* SoundCloud User Verified status
|
||||
*/
|
||||
verified: boolean;
|
||||
/**
|
||||
* SoundCloud User Description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* SoundCloud User First Name
|
||||
*/
|
||||
first_name: string;
|
||||
/**
|
||||
* SoundCloud User Full Name
|
||||
*/
|
||||
full_name: string;
|
||||
/**
|
||||
* SoundCloud User Last Name
|
||||
*/
|
||||
last_name: string;
|
||||
/**
|
||||
* SoundCloud User thumbnail URL
|
||||
*/
|
||||
thumbnail: string;
|
||||
}
|
||||
|
||||
interface SoundCloudTrackDeprecated {
|
||||
export interface SoundCloudTrackDeprecated {
|
||||
/**
|
||||
* SoundCloud Track fetched status
|
||||
*/
|
||||
fetched: boolean;
|
||||
/**
|
||||
* SoundCloud Track ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* SoundCloud Class type. == "track"
|
||||
*/
|
||||
type: 'track';
|
||||
}
|
||||
|
||||
export interface SoundCloudTrackFormat {
|
||||
/**
|
||||
* SoundCloud Track Format Url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Track Format preset
|
||||
*/
|
||||
preset: string;
|
||||
/**
|
||||
* SoundCloud Track Format Duration
|
||||
*/
|
||||
duration: number;
|
||||
/**
|
||||
* SoundCloud Track Format data containing protocol and mime_type
|
||||
*/
|
||||
format: {
|
||||
protocol: string;
|
||||
mime_type: string;
|
||||
};
|
||||
/**
|
||||
* SoundCloud Track Format quality
|
||||
*/
|
||||
quality: string;
|
||||
}
|
||||
/**
|
||||
* SoundCloud Track
|
||||
* SoundCloud Track Class
|
||||
*/
|
||||
export class SoundCloudTrack {
|
||||
/**
|
||||
* SoundCloud Track Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* SoundCloud Track ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* SoundCloud Track url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Track fetched status
|
||||
*/
|
||||
fetched: boolean;
|
||||
/**
|
||||
* SoundCloud Class type. === "track"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'user';
|
||||
/**
|
||||
* SoundCloud Track Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* SoundCloud Track Duration in miili seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
/**
|
||||
* SoundCloud Track formats data
|
||||
*/
|
||||
formats: SoundCloudTrackFormat[];
|
||||
/**
|
||||
* SoundCloud Track Publisher Data
|
||||
*/
|
||||
publisher: {
|
||||
name: string;
|
||||
id: number;
|
||||
@ -52,8 +134,18 @@ export class SoundCloudTrack {
|
||||
contains_music: boolean;
|
||||
writer_composer: string;
|
||||
} | null;
|
||||
/**
|
||||
* SoundCloud Track thumbnail
|
||||
*/
|
||||
thumbnail: string;
|
||||
/**
|
||||
* SoundCloud Track user data
|
||||
*/
|
||||
user: SoundCloudUser;
|
||||
/**
|
||||
* Constructor for SoundCloud Track Class
|
||||
* @param data JSON parsed track html data
|
||||
*/
|
||||
constructor(data: any) {
|
||||
this.name = data.title;
|
||||
this.id = data.id;
|
||||
@ -86,12 +178,14 @@ export class SoundCloudTrack {
|
||||
};
|
||||
this.thumbnail = data.artwork_url;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
/**
|
||||
* Converts class to JSON
|
||||
* @returns JSON parsed Data
|
||||
*/
|
||||
toJSON() : SoundTrackJSON {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
url: this.url,
|
||||
fetched: this.fetched,
|
||||
durationInMs: this.durationInMs,
|
||||
@ -104,20 +198,59 @@ export class SoundCloudTrack {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* SoundCloud Playlist
|
||||
* SoundCloud Playlist Class
|
||||
*/
|
||||
export class SoundCloudPlaylist {
|
||||
/**
|
||||
* SoundCloud Playlist Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* SoundCloud Playlist ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* SoundCloud Playlist URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Class type. == "playlist"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'user';
|
||||
/**
|
||||
* SoundCloud Playlist Sub type. == "album" for soundcloud albums
|
||||
*/
|
||||
sub_type: string;
|
||||
/**
|
||||
* SoundCloud Playlist Total Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* SoundCloud Playlist Total Duration in milli seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
client_id: string;
|
||||
/**
|
||||
* SoundCloud Playlist user data
|
||||
*/
|
||||
user: SoundCloudUser;
|
||||
/**
|
||||
* SoundCloud Playlist tracks [ It can be fetched or not fetched ]
|
||||
*/
|
||||
tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[];
|
||||
/**
|
||||
* SoundCloud Playlist tracks number
|
||||
*/
|
||||
tracksCount: number;
|
||||
/**
|
||||
* SoundCloud Client ID provided by user
|
||||
* @private
|
||||
*/
|
||||
private client_id: string;
|
||||
/**
|
||||
* Constructor for SoundCloud Playlist
|
||||
* @param data JSON parsed SoundCloud playlist data
|
||||
* @param client_id Provided SoundCloud Client ID
|
||||
*/
|
||||
constructor(data: any, client_id: string) {
|
||||
this.name = data.title;
|
||||
this.id = data.id;
|
||||
@ -153,8 +286,13 @@ export class SoundCloudPlaylist {
|
||||
});
|
||||
this.tracks = tracks;
|
||||
}
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
/**
|
||||
* Fetches all unfetched songs in a playlist.
|
||||
*
|
||||
* For fetching songs and getting all songs, see `fetched_tracks` property.
|
||||
* @returns playlist class
|
||||
*/
|
||||
async fetch(): Promise<SoundCloudPlaylist> {
|
||||
const work: any[] = [];
|
||||
for (let i = 0; i < this.tracks.length; i++) {
|
||||
if (!this.tracks[i].fetched) {
|
||||
@ -172,9 +310,12 @@ export class SoundCloudPlaylist {
|
||||
}
|
||||
}
|
||||
await Promise.allSettled(work);
|
||||
return this;
|
||||
}
|
||||
|
||||
get total_tracks() {
|
||||
/**
|
||||
* Get total no. of fetched tracks
|
||||
*/
|
||||
get total_tracks(): number {
|
||||
let count = 0;
|
||||
this.tracks.forEach((track) => {
|
||||
if (track instanceof SoundCloudTrack) count++;
|
||||
@ -182,12 +323,35 @@ export class SoundCloudPlaylist {
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
/**
|
||||
* Get all fetched tracks as a array.
|
||||
*
|
||||
* For getting all feetched tracks
|
||||
*
|
||||
* ```ts
|
||||
* const playlist = await play.soundcloud("playlist url")
|
||||
*
|
||||
* await playlist.fetch()
|
||||
*
|
||||
* const result = playlist.fetched_tracks
|
||||
* ```
|
||||
*/
|
||||
get fetched_tracks(): SoundCloudTrack[] {
|
||||
let result: SoundCloudTrack[] = [];
|
||||
this.tracks.forEach((track) => {
|
||||
if (track instanceof SoundCloudTrack) result.push(track);
|
||||
else return;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Converts Class to JSON data
|
||||
* @returns JSON parsed data
|
||||
*/
|
||||
toJSON(): PlaylistJSON {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
sub_type: this.sub_type,
|
||||
url: this.url,
|
||||
durationInMs: this.durationInMs,
|
||||
@ -201,18 +365,58 @@ export class SoundCloudPlaylist {
|
||||
/**
|
||||
* SoundCloud Stream class
|
||||
*/
|
||||
export class Stream {
|
||||
export class SoundCloudStream {
|
||||
/**
|
||||
* Readable Stream through which data passes
|
||||
*/
|
||||
stream: Readable;
|
||||
/**
|
||||
* Type of audio data that we recieved from normal youtube url.
|
||||
*/
|
||||
type: StreamType;
|
||||
/**
|
||||
* Dash Url containing segment urls.
|
||||
* @private
|
||||
*/
|
||||
private url: string;
|
||||
/**
|
||||
* Total time of downloaded segments data.
|
||||
* @private
|
||||
*/
|
||||
private downloaded_time: number;
|
||||
/**
|
||||
* Timer for looping code every 5 minutes
|
||||
* @private
|
||||
*/
|
||||
private timer: Timer;
|
||||
/**
|
||||
* Total segments Downloaded so far
|
||||
* @private
|
||||
*/
|
||||
private downloaded_segments: number;
|
||||
/**
|
||||
* Incoming message that we recieve.
|
||||
*
|
||||
* Storing this is essential.
|
||||
* This helps to destroy the TCP connection completely if you stopped player in between the stream
|
||||
* @private
|
||||
*/
|
||||
private request: IncomingMessage | null;
|
||||
/**
|
||||
* Array of segment time. Useful for calculating downloaded_time.
|
||||
*/
|
||||
private time: number[];
|
||||
/**
|
||||
* Array of segment_urls in dash file.
|
||||
*/
|
||||
private segment_urls: string[];
|
||||
/**
|
||||
* Constructor for SoundCloud Stream
|
||||
* @param url Dash url containing dash file.
|
||||
* @param type Stream Type
|
||||
*/
|
||||
constructor(url: string, type: StreamType = StreamType.Arbitrary) {
|
||||
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} });
|
||||
this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
this.downloaded_time = 0;
|
||||
@ -229,7 +433,10 @@ export class Stream {
|
||||
});
|
||||
this.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses SoundCloud dash file.
|
||||
* @private
|
||||
*/
|
||||
private async parser() {
|
||||
const response = await request(this.url).catch((err: Error) => {
|
||||
return err;
|
||||
@ -245,7 +452,9 @@ export class Stream {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts looping of code for getting all segments urls data
|
||||
*/
|
||||
private async start() {
|
||||
if (this.stream.destroyed) {
|
||||
this.cleanup();
|
||||
@ -253,12 +462,14 @@ export class Stream {
|
||||
}
|
||||
this.time = [];
|
||||
this.segment_urls = [];
|
||||
await this.parser();
|
||||
this.downloaded_time = 0;
|
||||
await this.parser();
|
||||
this.segment_urls.splice(0, this.downloaded_segments);
|
||||
this.loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Loop function for getting all segments urls data
|
||||
*/
|
||||
private async loop() {
|
||||
if (this.stream.destroyed) {
|
||||
this.cleanup();
|
||||
@ -290,7 +501,11 @@ export class Stream {
|
||||
this.stream.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This cleans every used variable in class.
|
||||
*
|
||||
* This is used to prevent re-use of this class and helping garbage collector to collect it.
|
||||
*/
|
||||
private cleanup() {
|
||||
this.timer.destroy();
|
||||
this.request?.destroy();
|
||||
@ -301,11 +516,19 @@ export class Stream {
|
||||
this.time = [];
|
||||
this.segment_urls = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses timer.
|
||||
* Stops running of loop.
|
||||
*
|
||||
* Useful if you don't want to get excess data to be stored in stream.
|
||||
*/
|
||||
pause() {
|
||||
this.timer.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes timer.
|
||||
* Starts running of loop.
|
||||
*/
|
||||
resume() {
|
||||
this.timer.resume();
|
||||
}
|
||||
|
||||
93
play-dl/SoundCloud/constants.ts
Normal file
93
play-dl/SoundCloud/constants.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { SoundCloudTrack, SoundCloudTrackDeprecated, SoundCloudTrackFormat, SoundCloudUser } from "./classes";
|
||||
|
||||
export interface SoundTrackJSON{
|
||||
/**
|
||||
* SoundCloud Track Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* SoundCloud Track ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* SoundCloud Track url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Track fetched status
|
||||
*/
|
||||
fetched: boolean;
|
||||
/**
|
||||
* SoundCloud Track Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* SoundCloud Track Duration in miili seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
/**
|
||||
* SoundCloud Track formats data
|
||||
*/
|
||||
formats: SoundCloudTrackFormat[];
|
||||
/**
|
||||
* SoundCloud Track Publisher Data
|
||||
*/
|
||||
publisher: {
|
||||
name: string;
|
||||
id: number;
|
||||
artist: string;
|
||||
contains_music: boolean;
|
||||
writer_composer: string;
|
||||
} | null;
|
||||
/**
|
||||
* SoundCloud Track thumbnail
|
||||
*/
|
||||
thumbnail: string;
|
||||
/**
|
||||
* SoundCloud Track user data
|
||||
*/
|
||||
user: SoundCloudUser;
|
||||
}
|
||||
|
||||
export interface PlaylistJSON{
|
||||
/**
|
||||
* SoundCloud Playlist Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* SoundCloud Playlist ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* SoundCloud Playlist URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SoundCloud Playlist Sub type. == "album" for soundcloud albums
|
||||
*/
|
||||
sub_type: string;
|
||||
/**
|
||||
* SoundCloud Playlist Total Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* SoundCloud Playlist Total Duration in milli seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
/**
|
||||
* SoundCloud Playlist user data
|
||||
*/
|
||||
user: SoundCloudUser;
|
||||
/**
|
||||
* SoundCloud Playlist tracks [ It can be fetched or not fetched ]
|
||||
*/
|
||||
tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[];
|
||||
/**
|
||||
* SoundCloud Playlist tracks number
|
||||
*/
|
||||
tracksCount: number;
|
||||
/**
|
||||
* SoundCloud Client ID provided by user
|
||||
* @private
|
||||
*/
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import fs from 'node:fs';
|
||||
import { StreamType } from '../YouTube/stream';
|
||||
import { request } from '../Request';
|
||||
import { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, Stream } from './classes';
|
||||
|
||||
import { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, SoundCloudStream } from './classes';
|
||||
let soundData: SoundDataOptions;
|
||||
if (fs.existsSync('.data/soundcloud.data')) {
|
||||
soundData = JSON.parse(fs.readFileSync('.data/soundcloud.data').toString());
|
||||
@ -69,7 +68,7 @@ export async function so_search(
|
||||
* @param quality Quality to select from
|
||||
* @returns SoundCloud Stream
|
||||
*/
|
||||
export async function stream(url: string, quality?: number): Promise<Stream> {
|
||||
export async function stream(url: string, quality?: number): Promise<SoundCloudStream> {
|
||||
const data = await soundcloud(url);
|
||||
|
||||
if (data instanceof SoundCloudPlaylist) throw new Error("Streams can't be created from Playlist url");
|
||||
@ -83,7 +82,7 @@ export async function stream(url: string, quality?: number): Promise<Stream> {
|
||||
const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')
|
||||
? StreamType.OggOpus
|
||||
: StreamType.Arbitrary;
|
||||
return new Stream(s_data.url, type);
|
||||
return new SoundCloudStream(s_data.url, type);
|
||||
}
|
||||
/**
|
||||
* Function to get Free Client ID of soundcloud.
|
||||
@ -101,11 +100,6 @@ export async function getFreeClientID(): Promise<string> {
|
||||
const data2 = await request(urls[urls.length - 1]);
|
||||
return data2.split(',client_id:"')[1].split('"')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for SoundCloud Stream
|
||||
*/
|
||||
export type SoundCloudStream = Stream;
|
||||
/**
|
||||
* Function for creating a Stream of soundcloud using a SoundCloud Track Class
|
||||
* @param data SoundCloud Track Class
|
||||
@ -122,7 +116,7 @@ export async function stream_from_info(data: SoundCloudTrack, quality?: number):
|
||||
const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')
|
||||
? StreamType.OggOpus
|
||||
: StreamType.Arbitrary;
|
||||
return new Stream(s_data.url, type);
|
||||
return new SoundCloudStream(s_data.url, type);
|
||||
}
|
||||
/**
|
||||
* Function to check client ID
|
||||
@ -172,3 +166,5 @@ function parseHlsFormats(data: SoundCloudTrackFormat[]) {
|
||||
export function setSoundCloudToken(options: SoundDataOptions) {
|
||||
soundData = options;
|
||||
}
|
||||
|
||||
export { SoundCloudTrack, SoundCloudPlaylist, SoundCloudStream }
|
||||
@ -1,45 +1,122 @@
|
||||
import { request } from '../Request';
|
||||
import { SpotifyDataOptions } from '.';
|
||||
import { AlbumJSON, PlaylistJSON, TrackJSON } from './constants';
|
||||
|
||||
interface SpotifyTrackAlbum {
|
||||
export interface SpotifyTrackAlbum {
|
||||
/**
|
||||
* Spotify Track Album name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Track Album url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Track Album id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Track Album release date
|
||||
*/
|
||||
release_date: string;
|
||||
/**
|
||||
* Spotify Track Album release date **precise**
|
||||
*/
|
||||
release_date_precision: string;
|
||||
/**
|
||||
* Spotify Track Album total tracks number
|
||||
*/
|
||||
total_tracks: number;
|
||||
}
|
||||
|
||||
interface SpotifyArtists {
|
||||
export interface SpotifyArtists {
|
||||
/**
|
||||
* Spotify Artist Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Artist Url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Artist ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface SpotifyThumbnail {
|
||||
export interface SpotifyThumbnail {
|
||||
/**
|
||||
* Spotify Thumbnail height
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* Spotify Thumbnail width
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* Spotify Thumbnail url
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface SpotifyCopyright {
|
||||
export interface SpotifyCopyright {
|
||||
/**
|
||||
* Spotify Copyright Text
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* Spotify Copyright Type
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
/**
|
||||
* Class for Spotify Track
|
||||
* Spotify Track Class
|
||||
*/
|
||||
export class SpotifyTrack {
|
||||
/**
|
||||
* Spotify Track Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Class type. == "track"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
/**
|
||||
* Spotify Track ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Track url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Track explicit info.
|
||||
*/
|
||||
explicit: boolean;
|
||||
/**
|
||||
* Spotify Track Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* Spotify Track Duration in milli seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
/**
|
||||
* Spotify Track Artists data [ array ]
|
||||
*/
|
||||
artists: SpotifyArtists[];
|
||||
/**
|
||||
* Spotify Track Album data
|
||||
*/
|
||||
album: SpotifyTrackAlbum | undefined;
|
||||
/**
|
||||
* Spotify Track Thumbnail Data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail | undefined;
|
||||
/**
|
||||
* Constructor for Spotify Track
|
||||
* @param data
|
||||
*/
|
||||
constructor(data: any) {
|
||||
this.name = data.name;
|
||||
this.id = data.id;
|
||||
@ -72,11 +149,10 @@ export class SpotifyTrack {
|
||||
else this.thumbnail = data.album.images[0];
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
toJSON() : TrackJSON {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
url: this.url,
|
||||
explicit: this.explicit,
|
||||
durationInMs: this.durationInMs,
|
||||
@ -88,20 +164,62 @@ export class SpotifyTrack {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Class for Spotify Playlist
|
||||
* Spotify Playlist Class
|
||||
*/
|
||||
export class SpotifyPlaylist {
|
||||
/**
|
||||
* Spotify Playlist Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Class type. == "playlist"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
/**
|
||||
* Spotify Playlist collaborative boolean.
|
||||
*/
|
||||
collaborative: boolean;
|
||||
/**
|
||||
* Spotify Playlist Description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Spotify Playlist URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Playlist ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Playlist Thumbnail Data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail;
|
||||
/**
|
||||
* Spotify Playlist Owner Artist data
|
||||
*/
|
||||
owner: SpotifyArtists;
|
||||
/**
|
||||
* Spotify Playlist total tracks Count
|
||||
*/
|
||||
tracksCount: number;
|
||||
/**
|
||||
* Spotify Playlist Spotify data
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private spotifyData: SpotifyDataOptions;
|
||||
/**
|
||||
* Spotify Playlist fetched tracks Map
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private fetched_tracks: Map<string, SpotifyTrack[]>;
|
||||
/**
|
||||
* Constructor for Spotify Playlist Class
|
||||
* @param data JSON parsed data of playlist
|
||||
* @param spotifyData Data about sporify token for furhter fetching.
|
||||
*/
|
||||
constructor(data: any, spotifyData: SpotifyDataOptions) {
|
||||
this.name = data.name;
|
||||
this.type = 'playlist';
|
||||
@ -124,7 +242,12 @@ export class SpotifyPlaylist {
|
||||
this.fetched_tracks.set('1', videos);
|
||||
this.spotifyData = spotifyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Spotify Playlist tracks more than 100 tracks.
|
||||
*
|
||||
* For getting all tracks in playlist, see `total_pages` property.
|
||||
* @returns Playlist Class.
|
||||
*/
|
||||
async fetch() {
|
||||
let fetching: number;
|
||||
if (this.tracksCount > 1000) fetching = 1000;
|
||||
@ -158,51 +281,131 @@ export class SpotifyPlaylist {
|
||||
await Promise.allSettled(work);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Playlist tracks are divided in pages.
|
||||
*
|
||||
* For example getting data of 101 - 200 videos in a playlist,
|
||||
*
|
||||
* ```ts
|
||||
* const playlist = await play.spotify('playlist url')
|
||||
*
|
||||
* await playlist.fetch()
|
||||
*
|
||||
* const result = playlist.page(2)
|
||||
* ```
|
||||
* @param num Page Number
|
||||
* @returns
|
||||
*/
|
||||
page(num: number) {
|
||||
if (!num) throw new Error('Page number is not provided');
|
||||
if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');
|
||||
return this.fetched_tracks.get(`${num}`);
|
||||
return this.fetched_tracks.get(`${num}`) as SpotifyTrack[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Playlist total no of pages in a playlist
|
||||
*
|
||||
* For getting all songs in a playlist,
|
||||
*
|
||||
* ```ts
|
||||
* const playlist = await play.spotify('playlist url')
|
||||
*
|
||||
* await playlist.fetch()
|
||||
*
|
||||
* const result = []
|
||||
*
|
||||
* for (let i = 0; i <= playlist.tota_pages; i++) {
|
||||
* result.push(playlist.page(i))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
get total_pages() {
|
||||
return this.fetched_tracks.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Playlist total no of tracks that have been fetched so far.
|
||||
*/
|
||||
get total_tracks() {
|
||||
const page_number: number = this.total_pages;
|
||||
return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
/**
|
||||
* Converts Class to JSON
|
||||
* @returns JSON data
|
||||
*/
|
||||
toJSON() : PlaylistJSON{
|
||||
return {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
collaborative: this.collaborative,
|
||||
description: this.description,
|
||||
url: this.url,
|
||||
id: this.id,
|
||||
thumbnail: this.thumbnail,
|
||||
owner: this.owner
|
||||
owner: this.owner,
|
||||
tracksCount : this.tracksCount
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Class for Spotify Album
|
||||
* Spotify Album Class
|
||||
*/
|
||||
export class SpotifyAlbum {
|
||||
/**
|
||||
* Spotify Album Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Class type. == "album"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
/**
|
||||
* Spotify Album url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Album id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Album Thumbnail data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail;
|
||||
/**
|
||||
* Spotify Album artists [ array ]
|
||||
*/
|
||||
artists: SpotifyArtists[];
|
||||
/**
|
||||
* Spotify Album copyright data [ array ]
|
||||
*/
|
||||
copyrights: SpotifyCopyright[];
|
||||
/**
|
||||
* Spotify Album Release date
|
||||
*/
|
||||
release_date: string;
|
||||
/**
|
||||
* Spotify Album Release Date **precise**
|
||||
*/
|
||||
release_date_precision: string;
|
||||
/**
|
||||
* Spotify Album total no of tracks
|
||||
*/
|
||||
tracksCount: number;
|
||||
/**
|
||||
* Spotify Album Spotify data
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private spotifyData: SpotifyDataOptions;
|
||||
/**
|
||||
* Spotify Album fetched tracks Map
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private fetched_tracks: Map<string, SpotifyTrack[]>;
|
||||
/**
|
||||
* Constructor for Spotify Album Class
|
||||
* @param data Json parsed album data
|
||||
* @param spotifyData Spotify credentials
|
||||
*/
|
||||
constructor(data: any, spotifyData: SpotifyDataOptions) {
|
||||
this.name = data.name;
|
||||
this.type = 'album';
|
||||
@ -230,7 +433,12 @@ export class SpotifyAlbum {
|
||||
this.fetched_tracks.set('1', videos);
|
||||
this.spotifyData = spotifyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Spotify Album tracks more than 50 tracks.
|
||||
*
|
||||
* For getting all tracks in album, see `total_pages` property.
|
||||
* @returns Album Class.
|
||||
*/
|
||||
async fetch() {
|
||||
let fetching: number;
|
||||
if (this.tracksCount > 500) fetching = 500;
|
||||
@ -264,25 +472,58 @@ export class SpotifyAlbum {
|
||||
await Promise.allSettled(work);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Album tracks are divided in pages.
|
||||
*
|
||||
* For example getting data of 51 - 100 videos in a album,
|
||||
*
|
||||
* ```ts
|
||||
* const album = await play.spotify('album url')
|
||||
*
|
||||
* await album.fetch()
|
||||
*
|
||||
* const result = album.page(2)
|
||||
* ```
|
||||
* @param num Page Number
|
||||
* @returns
|
||||
*/
|
||||
page(num: number) {
|
||||
if (!num) throw new Error('Page number is not provided');
|
||||
if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');
|
||||
return this.fetched_tracks.get(`${num}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Album total no of pages in a album
|
||||
*
|
||||
* For getting all songs in a album,
|
||||
*
|
||||
* ```ts
|
||||
* const album = await play.spotify('album url')
|
||||
*
|
||||
* await album.fetch()
|
||||
*
|
||||
* const result = []
|
||||
*
|
||||
* for (let i = 0; i <= album.tota_pages; i++) {
|
||||
* result.push(album.page(i))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
get total_pages() {
|
||||
return this.fetched_tracks.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify Album total no of tracks that have been fetched so far.
|
||||
*/
|
||||
get total_tracks() {
|
||||
const page_number: number = this.total_pages;
|
||||
return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
toJSON() : AlbumJSON {
|
||||
return {
|
||||
name: this.name,
|
||||
id : this.id,
|
||||
type: this.type,
|
||||
url: this.url,
|
||||
thumbnail: this.thumbnail,
|
||||
@ -290,7 +531,7 @@ export class SpotifyAlbum {
|
||||
copyrights: this.copyrights,
|
||||
release_date: this.release_date,
|
||||
release_date_precision: this.release_date_precision,
|
||||
total_tracks: this.total_tracks
|
||||
tracksCount : this.tracksCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
118
play-dl/Spotify/constants.ts
Normal file
118
play-dl/Spotify/constants.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { SpotifyArtists, SpotifyCopyright, SpotifyThumbnail, SpotifyTrackAlbum } from './classes'
|
||||
|
||||
export interface TrackJSON{
|
||||
/**
|
||||
* Spotify Track Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Track ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Track url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Track explicit info.
|
||||
*/
|
||||
explicit: boolean;
|
||||
/**
|
||||
* Spotify Track Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* Spotify Track Duration in milli seconds
|
||||
*/
|
||||
durationInMs: number;
|
||||
/**
|
||||
* Spotify Track Artists data [ array ]
|
||||
*/
|
||||
artists: SpotifyArtists[];
|
||||
/**
|
||||
* Spotify Track Album data
|
||||
*/
|
||||
album: SpotifyTrackAlbum | undefined;
|
||||
/**
|
||||
* Spotify Track Thumbnail Data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail | undefined;
|
||||
}
|
||||
|
||||
export interface PlaylistJSON {
|
||||
/**
|
||||
* Spotify Playlist Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Playlist collaborative boolean.
|
||||
*/
|
||||
collaborative: boolean;
|
||||
/**
|
||||
* Spotify Playlist Description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Spotify Playlist URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Playlist ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Playlist Thumbnail Data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail;
|
||||
/**
|
||||
* Spotify Playlist Owner Artist data
|
||||
*/
|
||||
owner: SpotifyArtists;
|
||||
/**
|
||||
* Spotify Playlist total tracks Count
|
||||
*/
|
||||
tracksCount: number;
|
||||
}
|
||||
|
||||
export interface AlbumJSON{
|
||||
/**
|
||||
* Spotify Album Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Spotify Class type. == "album"
|
||||
*/
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
/**
|
||||
* Spotify Album url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Spotify Album id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Spotify Album Thumbnail data
|
||||
*/
|
||||
thumbnail: SpotifyThumbnail;
|
||||
/**
|
||||
* Spotify Album artists [ array ]
|
||||
*/
|
||||
artists: SpotifyArtists[];
|
||||
/**
|
||||
* Spotify Album copyright data [ array ]
|
||||
*/
|
||||
copyrights: SpotifyCopyright[];
|
||||
/**
|
||||
* Spotify Album Release date
|
||||
*/
|
||||
release_date: string;
|
||||
/**
|
||||
* Spotify Album Release Date **precise**
|
||||
*/
|
||||
release_date_precision: string;
|
||||
/**
|
||||
* Spotify Album total no of tracks
|
||||
*/
|
||||
tracksCount: number;
|
||||
}
|
||||
@ -214,3 +214,5 @@ export function setSpotifyToken(options: SpotifyDataOptions) {
|
||||
spotifyData.file = false;
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
export { SpotifyTrack, SpotifyAlbum, SpotifyPlaylist }
|
||||
@ -1,30 +1,60 @@
|
||||
export interface ChannelIconInterface {
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube Channel Icon URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* YouTube Channel Icon Width
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* YouTube Channel Icon Height
|
||||
*/
|
||||
height: number;
|
||||
}
|
||||
/**
|
||||
* Class for YouTube Channel url
|
||||
* YouTube Channel Class
|
||||
*/
|
||||
export class YouTubeChannel {
|
||||
/**
|
||||
* YouTube Channel Title
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* YouTube Channel Verified status.
|
||||
*/
|
||||
verified?: boolean;
|
||||
/**
|
||||
* YouTube Channel artist if any.
|
||||
*/
|
||||
artist?: boolean;
|
||||
/**
|
||||
* YouTube Channel ID.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* YouTube Class type. == "channel"
|
||||
*/
|
||||
type: 'video' | 'playlist' | 'channel';
|
||||
/**
|
||||
* YouTube Channel Url
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube Channel Icon data.
|
||||
*/
|
||||
icon?: ChannelIconInterface;
|
||||
/**
|
||||
* YouTube Channel subscribers count.
|
||||
*/
|
||||
subscribers?: string;
|
||||
|
||||
constructor(data: any) {
|
||||
/**
|
||||
* YouTube Channel Constructor
|
||||
* @param data YouTube Channel data that we recieve from basic info or from search
|
||||
*/
|
||||
constructor(data: any = {}) {
|
||||
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
|
||||
this.type = 'channel';
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
private _patch(data: any): void {
|
||||
if (!data) data = {};
|
||||
|
||||
this.name = data.name || null;
|
||||
this.verified = !!data.verified || false;
|
||||
this.artist = !!data.artist || false;
|
||||
@ -45,21 +75,62 @@ export class YouTubeChannel {
|
||||
const def = this.icon.url.split('=s')[1].split('-c')[0];
|
||||
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Channel Class to channel name.
|
||||
* @returns name of channel
|
||||
*/
|
||||
toString(): string {
|
||||
return this.name || '';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
/**
|
||||
* Converts Channel Class to JSON format
|
||||
* @returns json data of the channel
|
||||
*/
|
||||
toJSON() : ChannelJSON {
|
||||
return {
|
||||
name: this.name,
|
||||
verified: this.verified,
|
||||
artist: this.artist,
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
iconURL: this.iconURL(),
|
||||
icon: this.icon,
|
||||
type: this.type,
|
||||
subscribers: this.subscribers
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface ChannelJSON{
|
||||
/**
|
||||
* YouTube Channel Title
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* YouTube Channel Verified status.
|
||||
*/
|
||||
verified?: boolean;
|
||||
/**
|
||||
* YouTube Channel artist if any.
|
||||
*/
|
||||
artist?: boolean;
|
||||
/**
|
||||
* YouTube Channel ID.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Type of Class [ Channel ]
|
||||
*/
|
||||
type: 'video' | 'playlist' | 'channel';
|
||||
/**
|
||||
* YouTube Channel Url
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube Channel Icon data.
|
||||
*/
|
||||
icon?: ChannelIconInterface;
|
||||
/**
|
||||
* YouTube Channel subscribers count.
|
||||
*/
|
||||
subscribers?: string;
|
||||
}
|
||||
@ -9,21 +9,69 @@ export interface FormatInterface {
|
||||
targetDurationSec: number;
|
||||
maxDvrDurationSec: number;
|
||||
}
|
||||
|
||||
export class LiveStreaming {
|
||||
/**
|
||||
* YouTube Live Stream class for playing audio from Live Stream videos.
|
||||
*/
|
||||
export class LiveStream {
|
||||
/**
|
||||
* Readable Stream through which data passes
|
||||
*/
|
||||
stream: Readable;
|
||||
/**
|
||||
* Type of audio data that we recieved from live stream youtube url.
|
||||
*/
|
||||
type: StreamType;
|
||||
/**
|
||||
* Base URL in dash manifest file.
|
||||
*/
|
||||
private base_url: string;
|
||||
/**
|
||||
* Given Dash URL.
|
||||
*/
|
||||
private url: string;
|
||||
/**
|
||||
* Interval to fetch data again to dash url.
|
||||
*/
|
||||
private interval: number;
|
||||
/**
|
||||
* Sequence count of urls in dash file.
|
||||
*/
|
||||
private packet_count: number;
|
||||
/**
|
||||
* Timer that creates loop from interval time provided.
|
||||
*/
|
||||
private timer: Timer;
|
||||
/**
|
||||
* Live Stream Video url.
|
||||
*/
|
||||
private video_url: string;
|
||||
/**
|
||||
* Timer used to update dash url so as to avoid 404 errors after long hours of streaming.
|
||||
*
|
||||
* It updates dash_url every 30 minutes.
|
||||
*/
|
||||
private dash_timer: Timer;
|
||||
/**
|
||||
* Segments of url that we recieve in dash file.
|
||||
*
|
||||
* base_url + segment_urls[0] = One complete url for one segment.
|
||||
*/
|
||||
private segments_urls: string[];
|
||||
/**
|
||||
* Incoming message that we recieve.
|
||||
*
|
||||
* Storing this is essential.
|
||||
* This helps to destroy the TCP connection completely if you stopped player in between the stream
|
||||
*/
|
||||
private request: IncomingMessage | null;
|
||||
/**
|
||||
* Live Stream Class Constructor
|
||||
* @param dash_url dash manifest URL
|
||||
* @param target_interval interval time for fetching dash data again
|
||||
* @param video_url Live Stream video url.
|
||||
*/
|
||||
constructor(dash_url: string, target_interval: number, video_url: string) {
|
||||
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} });
|
||||
this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
|
||||
this.type = StreamType.Arbitrary;
|
||||
this.url = dash_url;
|
||||
this.base_url = '';
|
||||
@ -44,7 +92,11 @@ export class LiveStreaming {
|
||||
});
|
||||
this.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dash url.
|
||||
*
|
||||
* Used by dash_timer for updating dash_url every 30 minutes.
|
||||
*/
|
||||
private async dash_updater() {
|
||||
const info = await video_info(this.video_url);
|
||||
if (
|
||||
@ -52,10 +104,14 @@ export class LiveStreaming {
|
||||
info.LiveStreamData.hlsManifestUrl !== null &&
|
||||
info.video_details.durationInSec === 0
|
||||
) {
|
||||
this.url = info.LiveStreamData.dashManifestUrl;
|
||||
this.url = info.LiveStreamData.dashManifestUrl as string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses data recieved from dash_url.
|
||||
*
|
||||
* Updates base_url , segments_urls array.
|
||||
*/
|
||||
private async dash_getter() {
|
||||
const response = await request(this.url);
|
||||
const audioFormat = response
|
||||
@ -68,7 +124,11 @@ export class LiveStreaming {
|
||||
this.segments_urls = list.replace(new RegExp('<SegmentURL media="', 'g'), '').split('"/>');
|
||||
if (this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* This cleans every used variable in class.
|
||||
*
|
||||
* This is used to prevent re-use of this class and helping garbage collector to collect it.
|
||||
*/
|
||||
private cleanup() {
|
||||
this.timer.destroy();
|
||||
this.dash_timer.destroy();
|
||||
@ -81,7 +141,12 @@ export class LiveStreaming {
|
||||
this.packet_count = 0;
|
||||
this.interval = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts function in Live Stream Class.
|
||||
*
|
||||
* Gets data from dash url and pass it to dash getter function.
|
||||
* Get data from complete segment url and pass data to Stream.
|
||||
*/
|
||||
private async start() {
|
||||
if (this.stream.destroyed) {
|
||||
this.cleanup();
|
||||
@ -116,25 +181,75 @@ export class LiveStreaming {
|
||||
|
||||
this.timer.reuse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated Functions
|
||||
*/
|
||||
pause() {}
|
||||
/**
|
||||
* Deprecated Functions
|
||||
*/
|
||||
resume() {}
|
||||
}
|
||||
/**
|
||||
* Class for YouTube Stream
|
||||
* YouTube Stream Class for playing audio from normal videos.
|
||||
*/
|
||||
export class Stream {
|
||||
/**
|
||||
* Readable Stream through which data passes
|
||||
*/
|
||||
stream: Readable;
|
||||
/**
|
||||
* Type of audio data that we recieved from normal youtube url.
|
||||
*/
|
||||
type: StreamType;
|
||||
/**
|
||||
* Audio Endpoint Format Url to get data from.
|
||||
*/
|
||||
private url: string;
|
||||
/**
|
||||
* Used to calculate no of bytes data that we have recieved
|
||||
*/
|
||||
private bytes_count: number;
|
||||
/**
|
||||
* Calculate per second bytes by using contentLength (Total bytes) / Duration (in seconds)
|
||||
*/
|
||||
private per_sec_bytes: number;
|
||||
/**
|
||||
* Total length of audio file in bytes
|
||||
*/
|
||||
private content_length: number;
|
||||
/**
|
||||
* YouTube video url. [ Used only for retrying purposes only. ]
|
||||
*/
|
||||
private video_url: string;
|
||||
/**
|
||||
* Timer for looping data every 265 seconds.
|
||||
*/
|
||||
private timer: Timer;
|
||||
/**
|
||||
* Quality given by user. [ Used only for retrying purposes only. ]
|
||||
*/
|
||||
private quality: number;
|
||||
/**
|
||||
* Proxy config given by user. [ Used only for retrying purposes only. ]
|
||||
*/
|
||||
private proxy: Proxy[] | undefined;
|
||||
/**
|
||||
* Incoming message that we recieve.
|
||||
*
|
||||
* Storing this is essential.
|
||||
* This helps to destroy the TCP connection completely if you stopped player in between the stream
|
||||
*/
|
||||
private request: IncomingMessage | null;
|
||||
/**
|
||||
* YouTube Stream Class constructor
|
||||
* @param url Audio Endpoint url.
|
||||
* @param type Type of Stream
|
||||
* @param duration Duration of audio playback [ in seconds ]
|
||||
* @param contentLength Total length of Audio file in bytes.
|
||||
* @param video_url YouTube video url.
|
||||
* @param options Options provided to stream function.
|
||||
*/
|
||||
constructor(
|
||||
url: string,
|
||||
type: StreamType,
|
||||
@ -143,7 +258,7 @@ export class Stream {
|
||||
video_url: string,
|
||||
options: StreamOptions
|
||||
) {
|
||||
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} });
|
||||
this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
|
||||
this.url = url;
|
||||
this.quality = options.quality as number;
|
||||
this.proxy = options.proxy || undefined;
|
||||
@ -163,19 +278,29 @@ export class Stream {
|
||||
});
|
||||
this.loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry if we get 404 or 403 Errors.
|
||||
*/
|
||||
private async retry() {
|
||||
const info = await video_info(this.video_url, { proxy: this.proxy });
|
||||
const audioFormat = parseAudioFormats(info.format);
|
||||
this.url = audioFormat[this.quality].url;
|
||||
}
|
||||
|
||||
/**
|
||||
* This cleans every used variable in class.
|
||||
*
|
||||
* This is used to prevent re-use of this class and helping garbage collector to collect it.
|
||||
*/
|
||||
private cleanup() {
|
||||
this.request?.destroy();
|
||||
this.request = null;
|
||||
this.url = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting data from audio endpoint url and passing it to stream.
|
||||
*
|
||||
* If 404 or 403 occurs, it will retry again.
|
||||
*/
|
||||
private async loop() {
|
||||
if (this.stream.destroyed) {
|
||||
this.timer.destroy();
|
||||
@ -226,24 +351,62 @@ export class Stream {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses timer.
|
||||
* Stops running of loop.
|
||||
*
|
||||
* Useful if you don't want to get excess data to be stored in stream.
|
||||
*/
|
||||
pause() {
|
||||
this.timer.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes timer.
|
||||
* Starts running of loop.
|
||||
*/
|
||||
resume() {
|
||||
this.timer.resume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer Class.
|
||||
*
|
||||
* setTimeout + extra features ( re-starting, pausing, resuming ).
|
||||
*/
|
||||
export class Timer {
|
||||
/**
|
||||
* Boolean for checking if Timer is destroyed or not.
|
||||
*/
|
||||
private destroyed: boolean;
|
||||
/**
|
||||
* Boolean for checking if Timer is paused or not.
|
||||
*/
|
||||
private paused: boolean;
|
||||
/**
|
||||
* setTimeout function
|
||||
*/
|
||||
private timer: NodeJS.Timer;
|
||||
/**
|
||||
* Callback to be executed once timer finishes.
|
||||
*/
|
||||
private callback: () => void;
|
||||
/**
|
||||
* Seconds time when it is started.
|
||||
*/
|
||||
private time_start: number;
|
||||
/**
|
||||
* Total time left.
|
||||
*/
|
||||
private time_left: number;
|
||||
/**
|
||||
* Total time given by user [ Used only for re-using timer. ]
|
||||
*/
|
||||
private time_total: number;
|
||||
/**
|
||||
* Constructor for Timer Class
|
||||
* @param callback Function to execute when timer is up.
|
||||
* @param time Total time to wait before execution.
|
||||
*/
|
||||
constructor(callback: () => void, time: number) {
|
||||
this.callback = callback;
|
||||
this.time_total = time;
|
||||
@ -253,7 +416,10 @@ export class Timer {
|
||||
this.time_start = process.hrtime()[0];
|
||||
this.timer = setTimeout(this.callback, this.time_total * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses Timer
|
||||
* @returns Boolean to tell that if it is paused or not.
|
||||
*/
|
||||
pause() {
|
||||
if (!this.paused && !this.destroyed) {
|
||||
this.paused = true;
|
||||
@ -262,7 +428,10 @@ export class Timer {
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes Timer
|
||||
* @returns Boolean to tell that if it is resumed or not.
|
||||
*/
|
||||
resume() {
|
||||
if (this.paused && !this.destroyed) {
|
||||
this.paused = false;
|
||||
@ -271,7 +440,10 @@ export class Timer {
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusing of timer
|
||||
* @returns Boolean to tell if it is re-used or not.
|
||||
*/
|
||||
reuse() {
|
||||
if (!this.destroyed) {
|
||||
clearTimeout(this.timer);
|
||||
@ -282,7 +454,11 @@ export class Timer {
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy timer.
|
||||
*
|
||||
* It can't be used again.
|
||||
*/
|
||||
destroy() {
|
||||
clearTimeout(this.timer);
|
||||
this.destroyed = true;
|
||||
|
||||
@ -2,35 +2,77 @@ import { getPlaylistVideos, getContinuationToken } from '../utils/extractor';
|
||||
import { request } from '../../Request';
|
||||
import { YouTubeChannel } from './Channel';
|
||||
import { YouTubeVideo } from './Video';
|
||||
import { YouTubeThumbnail } from './Thumbnail';
|
||||
const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key=';
|
||||
/**
|
||||
* Class for YouTube Playlist url
|
||||
* YouTube Playlist Class containing vital informations about playlist.
|
||||
*/
|
||||
export class YouTubePlayList {
|
||||
/**
|
||||
* YouTube Playlist ID
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* YouTube Playlist Name
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* YouTube Class type. == "playlist"
|
||||
*/
|
||||
type: 'video' | 'playlist' | 'channel';
|
||||
/**
|
||||
* Total no of videos in that playlist
|
||||
*/
|
||||
videoCount?: number;
|
||||
/**
|
||||
* Time when playlist was last updated
|
||||
*/
|
||||
lastUpdate?: string;
|
||||
/**
|
||||
* Total views of that playlist
|
||||
*/
|
||||
views?: number;
|
||||
/**
|
||||
* YouTube Playlist url
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube Playlist url with starting video url.
|
||||
*/
|
||||
link?: string;
|
||||
/**
|
||||
* YouTube Playlist channel data
|
||||
*/
|
||||
channel?: YouTubeChannel;
|
||||
thumbnail?: {
|
||||
id: string | undefined;
|
||||
width: number | undefined;
|
||||
height: number | undefined;
|
||||
url: string | undefined;
|
||||
};
|
||||
private videos?: [];
|
||||
/**
|
||||
* YouTube Playlist thumbnail Data
|
||||
*/
|
||||
thumbnail?: YouTubeThumbnail
|
||||
/**
|
||||
* Videos array containing data of first 100 videos
|
||||
*/
|
||||
private videos?: YouTubeVideo[];
|
||||
/**
|
||||
* Map contaning data of all fetched videos
|
||||
*/
|
||||
private fetched_videos: Map<string, YouTubeVideo[]>;
|
||||
/**
|
||||
* Token containing API key, Token, ClientVersion.
|
||||
*/
|
||||
private _continuation: {
|
||||
api?: string;
|
||||
token?: string;
|
||||
clientVersion?: string;
|
||||
} = {};
|
||||
/**
|
||||
* Total no of pages count.
|
||||
*/
|
||||
private __count: number;
|
||||
|
||||
/**
|
||||
* Constructor for YouTube Playlist Class
|
||||
* @param data Json Parsed YouTube Playlist data
|
||||
* @param searchResult If the data is from search or not
|
||||
*/
|
||||
constructor(data: any, searchResult = false) {
|
||||
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
|
||||
this.__count = 0;
|
||||
@ -39,7 +81,10 @@ export class YouTubePlayList {
|
||||
if (searchResult) this.__patchSearch(data);
|
||||
else this.__patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates variable according to a normal data.
|
||||
* @param data Json Parsed YouTube Playlist data
|
||||
*/
|
||||
private __patch(data: any) {
|
||||
this.id = data.id || undefined;
|
||||
this.url = data.url || undefined;
|
||||
@ -57,7 +102,10 @@ export class YouTubePlayList {
|
||||
this._continuation.token = data.continuation?.token ?? undefined;
|
||||
this._continuation.clientVersion = data.continuation?.clientVersion ?? '<important data>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates variable according to a searched data.
|
||||
* @param data Json Parsed YouTube Playlist data
|
||||
*/
|
||||
private __patchSearch(data: any) {
|
||||
this.id = data.id || undefined;
|
||||
this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;
|
||||
@ -70,7 +118,13 @@ export class YouTubePlayList {
|
||||
this.lastUpdate = undefined;
|
||||
this.views = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next segment of videos from playlist and returns parsed data.
|
||||
* @param limit Total no of videos to parse.
|
||||
*
|
||||
* Default = Infinity
|
||||
* @returns Array of YouTube Video Class
|
||||
*/
|
||||
async next(limit = Infinity): Promise<YouTubeVideo[]> {
|
||||
if (!this._continuation || !this._continuation.token) return [];
|
||||
|
||||
@ -101,49 +155,139 @@ export class YouTubePlayList {
|
||||
this._continuation.token = getContinuationToken(contents);
|
||||
return playlist_videos;
|
||||
}
|
||||
|
||||
async fetch(max = Infinity) {
|
||||
/**
|
||||
* Fetches remaining data from playlist
|
||||
*
|
||||
* For fetching and getting all songs data, see `total_pages` property.
|
||||
* @param max Max no of videos to fetch
|
||||
*
|
||||
* Default = Infinity
|
||||
* @returns
|
||||
*/
|
||||
async fetch(max = Infinity): Promise<YouTubePlayList> {
|
||||
const continuation = this._continuation.token;
|
||||
if (!continuation) return this;
|
||||
if (max < 1) max = Infinity;
|
||||
|
||||
while (typeof this._continuation.token === 'string' && this._continuation.token.length) {
|
||||
if ((this.videos?.length as number) >= max) break;
|
||||
this.__count++;
|
||||
const res = await this.next();
|
||||
max -= res.length;
|
||||
if(max <= 0) break;
|
||||
if (!res.length) break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube Playlist is divided into pages.
|
||||
*
|
||||
* For example, if you want to get 101 - 200 songs
|
||||
*
|
||||
* ```ts
|
||||
* const playlist = await play.playlist_info('playlist url')
|
||||
*
|
||||
* await playlist.fetch()
|
||||
*
|
||||
* const result = playlist.page(2)
|
||||
* ```
|
||||
* @param number Page number
|
||||
* @returns Array of YouTube Video Class
|
||||
*/
|
||||
page(number: number): YouTubeVideo[] {
|
||||
if (!number) throw new Error('Page number is not provided');
|
||||
if (!this.fetched_videos.has(`${number}`)) throw new Error('Given Page number is invalid');
|
||||
return this.fetched_videos.get(`${number}`) as YouTubeVideo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets total no of pages in that playlist class.
|
||||
*
|
||||
* For getting all songs in a playlist
|
||||
*
|
||||
* ```ts
|
||||
* const playlist = await play.playlist_info('playlist url');
|
||||
*
|
||||
* await playlist.fetch();
|
||||
*
|
||||
* let result = [];
|
||||
*
|
||||
* for (let i = 0; i <= playlist.total_pages; i++) {
|
||||
* result.push(playlist.page(i));
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
get total_pages() {
|
||||
return this.fetched_videos.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* This tells total no of videos that have been fetched so far.
|
||||
*
|
||||
* This can be equal to videosCount if all videos in playlist have been fetched and they are not hidden.
|
||||
*/
|
||||
get total_videos() {
|
||||
const page_number: number = this.total_pages;
|
||||
return (page_number - 1) * 100 + (this.fetched_videos.get(`${page_number}`) as YouTubeVideo[]).length;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
/**
|
||||
* Converts Playlist Class to a json parsed data.
|
||||
* @returns
|
||||
*/
|
||||
toJSON() : PlaylistJSON {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
thumbnail: this.thumbnail,
|
||||
channel: {
|
||||
name: this.channel?.name,
|
||||
id: this.channel?.id,
|
||||
icon: this.channel?.iconURL()
|
||||
},
|
||||
thumbnail: this.thumbnail?.toJSON() || this.thumbnail,
|
||||
channel: this.channel,
|
||||
url: this.url,
|
||||
videos: this.videos
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface PlaylistJSON{
|
||||
/**
|
||||
* YouTube Playlist ID
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* YouTube Playlist Name
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Total no of videos in that playlist
|
||||
*/
|
||||
videoCount?: number;
|
||||
/**
|
||||
* Time when playlist was last updated
|
||||
*/
|
||||
lastUpdate?: string;
|
||||
/**
|
||||
* Total views of that playlist
|
||||
*/
|
||||
views?: number;
|
||||
/**
|
||||
* YouTube Playlist url
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube Playlist url with starting video url.
|
||||
*/
|
||||
link?: string;
|
||||
/**
|
||||
* YouTube Playlist channel data
|
||||
*/
|
||||
channel?: YouTubeChannel;
|
||||
/**
|
||||
* YouTube Playlist thumbnail Data
|
||||
*/
|
||||
thumbnail?: {
|
||||
id: string | undefined;
|
||||
width: number | undefined;
|
||||
height: number | undefined;
|
||||
url: string | undefined;
|
||||
};
|
||||
/**
|
||||
* first 100 videos in that playlist
|
||||
*/
|
||||
videos? : YouTubeVideo[]
|
||||
}
|
||||
22
play-dl/YouTube/classes/Thumbnail.ts
Normal file
22
play-dl/YouTube/classes/Thumbnail.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export class YouTubeThumbnail {
|
||||
id : string;
|
||||
url : string;
|
||||
width : number;
|
||||
height : number;
|
||||
|
||||
constructor(data : any){
|
||||
this.id = data.id
|
||||
this.url = data.url
|
||||
this.width = data.width
|
||||
this.height = data.height
|
||||
}
|
||||
|
||||
toJSON(){
|
||||
return {
|
||||
id : this.id,
|
||||
url : this.url,
|
||||
width : this.width,
|
||||
height : this.height
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,56 +1,145 @@
|
||||
import { YouTubeChannel } from './Channel';
|
||||
import { YouTubeThumbnail } from './Thumbnail';
|
||||
|
||||
interface VideoOptions {
|
||||
/**
|
||||
* YouTube Video ID
|
||||
*/
|
||||
id?: string;
|
||||
url?: string;
|
||||
/**
|
||||
* YouTube video url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* YouTube Video title
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* YouTube Video description.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* YouTube Video Duration Formatted
|
||||
*/
|
||||
durationRaw: string;
|
||||
/**
|
||||
* YouTube Video Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* YouTube Video Uploaded Date
|
||||
*/
|
||||
uploadedAt?: string;
|
||||
/**
|
||||
* YouTube Views
|
||||
*/
|
||||
views: number;
|
||||
/**
|
||||
* YouTube Thumbnail Data
|
||||
*/
|
||||
thumbnail?: {
|
||||
id: string | undefined;
|
||||
width: number | undefined;
|
||||
height: number | undefined;
|
||||
url: string | undefined;
|
||||
};
|
||||
channel?: any;
|
||||
type: string;
|
||||
ratings: {
|
||||
/**
|
||||
* YouTube Video's uploader Channel Data
|
||||
*/
|
||||
channel?: YouTubeChannel;
|
||||
/**
|
||||
* YouTube Video's likes
|
||||
*/
|
||||
likes: number;
|
||||
/**
|
||||
* YouTube Video's dislikes
|
||||
*/
|
||||
dislikes: number;
|
||||
};
|
||||
/**
|
||||
* YouTube Video live status
|
||||
*/
|
||||
live: boolean;
|
||||
/**
|
||||
* YouTube Video private status
|
||||
*/
|
||||
private: boolean;
|
||||
/**
|
||||
* YouTube Video tags
|
||||
*/
|
||||
tags: string[];
|
||||
}
|
||||
/**
|
||||
* Class for YouTube Video url
|
||||
*/
|
||||
export class YouTubeVideo {
|
||||
/**
|
||||
* YouTube Video ID
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* YouTube video url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* YouTube Class type. == "video"
|
||||
*/
|
||||
type: 'video' | 'playlist' | 'channel';
|
||||
/**
|
||||
* YouTube Video title
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* YouTube Video description.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* YouTube Video Duration Formatted
|
||||
*/
|
||||
durationRaw: string;
|
||||
/**
|
||||
* YouTube Video Duration in seconds
|
||||
*/
|
||||
durationInSec: number;
|
||||
/**
|
||||
* YouTube Video Uploaded Date
|
||||
*/
|
||||
uploadedAt?: string;
|
||||
/**
|
||||
* YouTube Views
|
||||
*/
|
||||
views: number;
|
||||
thumbnail?: {
|
||||
id: string | undefined;
|
||||
width: number | undefined;
|
||||
height: number | undefined;
|
||||
url: string | undefined;
|
||||
};
|
||||
/**
|
||||
* YouTube Thumbnail Data
|
||||
*/
|
||||
thumbnail?: YouTubeThumbnail;
|
||||
/**
|
||||
* YouTube Video's uploader Channel Data
|
||||
*/
|
||||
channel?: YouTubeChannel;
|
||||
/**
|
||||
* YouTube Video's likes
|
||||
*/
|
||||
likes: number;
|
||||
/**
|
||||
* YouTube Video's dislikes
|
||||
*/
|
||||
dislikes: number;
|
||||
/**
|
||||
* YouTube Video live status
|
||||
*/
|
||||
live: boolean;
|
||||
/**
|
||||
* YouTube Video private status
|
||||
*/
|
||||
private: boolean;
|
||||
/**
|
||||
* YouTube Video tags
|
||||
*/
|
||||
tags: string[];
|
||||
|
||||
/**
|
||||
* Constructor for YouTube Video Class
|
||||
* @param data JSON parsed data.
|
||||
*/
|
||||
constructor(data: any) {
|
||||
if (!data) throw new Error(`Can not initiate ${this.constructor.name} without data`);
|
||||
|
||||
@ -71,12 +160,18 @@ export class YouTubeVideo {
|
||||
this.private = !!data.private;
|
||||
this.tags = data.tags || [];
|
||||
}
|
||||
|
||||
get toString(): string {
|
||||
/**
|
||||
* Converts class to title name of video.
|
||||
* @returns Title name
|
||||
*/
|
||||
toString(): string {
|
||||
return this.url || '';
|
||||
}
|
||||
|
||||
get toJSON(): VideoOptions {
|
||||
/**
|
||||
* Converts class to JSON data
|
||||
* @returns JSON data.
|
||||
*/
|
||||
toJSON(): VideoOptions {
|
||||
return {
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
@ -85,15 +180,12 @@ export class YouTubeVideo {
|
||||
durationInSec: this.durationInSec,
|
||||
durationRaw: this.durationRaw,
|
||||
uploadedAt: this.uploadedAt,
|
||||
thumbnail: this.thumbnail,
|
||||
thumbnail: this.thumbnail?.toJSON() || this.thumbnail,
|
||||
channel: this.channel,
|
||||
views: this.views,
|
||||
type: this.type,
|
||||
tags: this.tags,
|
||||
ratings: {
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes
|
||||
},
|
||||
dislikes: this.dislikes,
|
||||
live: this.live,
|
||||
private: this.private
|
||||
};
|
||||
|
||||
@ -2,3 +2,6 @@ export { stream, stream_from_info, YouTubeStream } from './stream';
|
||||
export * from './utils';
|
||||
export { YouTube } from './search';
|
||||
export { cookieHeaders } from './utils/cookie';
|
||||
export { YouTubeVideo } from './classes/Video'
|
||||
export { YouTubePlayList } from './classes/Playlist'
|
||||
export { YouTubeChannel } from './classes/Channel'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { video_info } from '.';
|
||||
import { LiveStreaming, Stream } from './classes/LiveStream';
|
||||
import { LiveStream, Stream } from './classes/LiveStream';
|
||||
import { ProxyOptions as Proxy } from './../Request';
|
||||
import { InfoData } from './utils/constants';
|
||||
|
||||
export enum StreamType {
|
||||
Arbitrary = 'arbitrary',
|
||||
@ -16,16 +17,6 @@ export interface StreamOptions {
|
||||
htmldata?: boolean;
|
||||
}
|
||||
|
||||
export interface InfoData {
|
||||
LiveStreamData: {
|
||||
isLive: boolean;
|
||||
dashManifestUrl: string;
|
||||
hlsManifestUrl: string;
|
||||
};
|
||||
html5player: string;
|
||||
format: any[];
|
||||
video_details: any;
|
||||
}
|
||||
/**
|
||||
* Command to find audio formats from given format array
|
||||
* @param formats Formats to search from
|
||||
@ -46,7 +37,7 @@ export function parseAudioFormats(formats: any[]) {
|
||||
/**
|
||||
* Type for YouTube Stream
|
||||
*/
|
||||
export type YouTubeStream = Stream | LiveStreaming;
|
||||
export type YouTubeStream = Stream | LiveStream;
|
||||
/**
|
||||
* Stream command for YouTube
|
||||
* @param url YouTube URL
|
||||
@ -58,12 +49,12 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
|
||||
const final: any[] = [];
|
||||
if (
|
||||
info.LiveStreamData.isLive === true &&
|
||||
info.LiveStreamData.hlsManifestUrl !== null &&
|
||||
info.LiveStreamData.dashManifestUrl !== null &&
|
||||
info.video_details.durationInSec === 0
|
||||
) {
|
||||
return new LiveStreaming(
|
||||
return new LiveStream(
|
||||
info.LiveStreamData.dashManifestUrl,
|
||||
info.format[info.format.length - 1].targetDurationSec,
|
||||
info.format[info.format.length - 1].targetDurationSec as number,
|
||||
info.video_details.url
|
||||
);
|
||||
}
|
||||
@ -95,12 +86,12 @@ export async function stream_from_info(info: InfoData, options: StreamOptions =
|
||||
const final: any[] = [];
|
||||
if (
|
||||
info.LiveStreamData.isLive === true &&
|
||||
info.LiveStreamData.hlsManifestUrl !== null &&
|
||||
info.LiveStreamData.dashManifestUrl !== null &&
|
||||
info.video_details.durationInSec === 0
|
||||
) {
|
||||
return new LiveStreaming(
|
||||
return new LiveStream(
|
||||
info.LiveStreamData.dashManifestUrl,
|
||||
info.format[info.format.length - 1].targetDurationSec,
|
||||
info.format[info.format.length - 1].targetDurationSec as number,
|
||||
info.video_details.url
|
||||
);
|
||||
}
|
||||
|
||||
39
play-dl/YouTube/utils/constants.ts
Normal file
39
play-dl/YouTube/utils/constants.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { YouTubeVideo } from "../classes/Video";
|
||||
|
||||
export interface LiveStreamData {
|
||||
isLive: boolean;
|
||||
dashManifestUrl: string | null
|
||||
hlsManifestUrl: string | null
|
||||
}
|
||||
|
||||
export interface formatData {
|
||||
itag: number;
|
||||
mimeType: string
|
||||
bitrate: number
|
||||
width: number
|
||||
height: number
|
||||
lastModified: string
|
||||
contentLength: string
|
||||
quality: string
|
||||
fps: number
|
||||
qualityLabel: string
|
||||
projectionType: string
|
||||
averageBitrate: number
|
||||
audioQuality: string
|
||||
approxDurationMs: string
|
||||
audioSampleRate: string
|
||||
audioChannels: number
|
||||
url : string
|
||||
signatureCipher : string;
|
||||
cipher : string;
|
||||
loudnessDb : number;
|
||||
targetDurationSec : number;
|
||||
}
|
||||
|
||||
export interface InfoData{
|
||||
LiveStreamData : LiveStreamData
|
||||
html5player : string
|
||||
format : Partial<formatData>[]
|
||||
video_details : YouTubeVideo
|
||||
related_videos: string[]
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { ProxyOptions as Proxy, request } from './../../Request/index';
|
||||
import { format_decipher } from './cipher';
|
||||
import { YouTubeVideo } from '../classes/Video';
|
||||
import { YouTubePlayList } from '../classes/Playlist';
|
||||
import { InfoData } from '../stream';
|
||||
import { InfoData } from './constants';
|
||||
|
||||
interface InfoOptions {
|
||||
proxy?: Proxy[];
|
||||
@ -79,7 +79,7 @@ export function extractID(url: string): string {
|
||||
* @param options cookie and proxy parameters to add
|
||||
* @returns Data containing video_details, LiveStreamData and formats of video url.
|
||||
*/
|
||||
export async function video_basic_info(url: string, options: InfoOptions = {}) {
|
||||
export async function video_basic_info(url: string, options: InfoOptions = {}) : Promise<InfoData> {
|
||||
let body: string;
|
||||
if (options.htmldata) {
|
||||
body = url;
|
||||
@ -187,7 +187,7 @@ function parseSeconds(seconds: number): string {
|
||||
* @param options cookie and proxy parameters to add
|
||||
* @returns Data containing video_details, LiveStreamData and formats of video url.
|
||||
*/
|
||||
export async function video_info(url: string, options: InfoOptions = {}) {
|
||||
export async function video_info(url: string, options: InfoOptions = {}): Promise<InfoData> {
|
||||
const data = await video_basic_info(url, options);
|
||||
if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) {
|
||||
return data;
|
||||
|
||||
@ -7,11 +7,14 @@ export {
|
||||
extractID,
|
||||
YouTube,
|
||||
YouTubeStream,
|
||||
cookieHeaders
|
||||
cookieHeaders,
|
||||
YouTubeChannel,
|
||||
YouTubePlayList,
|
||||
YouTubeVideo
|
||||
} from './YouTube';
|
||||
export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify';
|
||||
export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID } from './SoundCloud';
|
||||
export { deezer, dz_validate, dz_search, dz_advanced_track_search, Deezer } from './Deezer';
|
||||
export { spotify, sp_validate, refreshToken, is_expired, SpotifyAlbum, SpotifyPlaylist, SpotifyTrack, Spotify } from './Spotify';
|
||||
export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID, SoundCloudPlaylist, SoundCloudTrack } from './SoundCloud';
|
||||
export { deezer, dz_validate, dz_advanced_track_search, Deezer, DeezerTrack, DeezerPlaylist, DeezerAlbum } from './Deezer';
|
||||
export { setToken } from './token';
|
||||
|
||||
enum AudioPlayerStatus {
|
||||
@ -30,6 +33,7 @@ interface SearchOptions {
|
||||
soundcloud?: 'tracks' | 'playlists' | 'albums';
|
||||
deezer?: 'track' | 'playlist' | 'album';
|
||||
};
|
||||
fuzzy?: boolean;
|
||||
}
|
||||
|
||||
import readline from 'node:readline';
|
||||
@ -46,11 +50,17 @@ import {
|
||||
} from '.';
|
||||
import { SpotifyAuthorize, sp_search } from './Spotify';
|
||||
import { check_id, so_search, stream as so_stream, stream_from_info as so_stream_info } from './SoundCloud';
|
||||
import { InfoData, stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';
|
||||
import { SoundCloudTrack } from './SoundCloud/classes';
|
||||
import { stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';
|
||||
import { SoundCloudPlaylist, SoundCloudTrack } from './SoundCloud/classes';
|
||||
import { yt_search } from './YouTube/search';
|
||||
import { EventEmitter } from 'stream';
|
||||
import { Deezer, dz_search, dz_validate } from './Deezer';
|
||||
import { InfoData } from './YouTube/utils/constants';
|
||||
import { YouTubeVideo } from './YouTube/classes/Video';
|
||||
import { YouTubePlayList } from './YouTube/classes/Playlist';
|
||||
import { YouTubeChannel } from './YouTube/classes/Channel';
|
||||
import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './Spotify/classes';
|
||||
import { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './Deezer/classes';
|
||||
/**
|
||||
* Main stream Command for streaming through various sources
|
||||
* @param url The video / track url to make stream of
|
||||
@ -79,7 +89,21 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
|
||||
* @param query string to search.
|
||||
* @param options contains limit and source to choose.
|
||||
* @returns Array of YouTube or Spotify or SoundCloud or Deezer
|
||||
deezer?: 'track' | 'playlist' | 'album';
|
||||
*/
|
||||
|
||||
export async function search( query: string, options: { source : { deezer : "album" } } & SearchOptions) : Promise<DeezerAlbum[]>;
|
||||
export async function search( query: string, options: { source : { deezer : "playlist" } } & SearchOptions) : Promise<DeezerPlaylist[]>;
|
||||
export async function search( query: string, options: { source : { deezer : "track" } } & SearchOptions) : Promise<DeezerTrack[]>;
|
||||
export async function search( query: string, options: { source : { soundcloud : "albums" } } & SearchOptions) : Promise<SoundCloudPlaylist[]>;
|
||||
export async function search( query: string, options: { source : { soundcloud : "playlists" } } & SearchOptions) : Promise<SoundCloudPlaylist[]>;
|
||||
export async function search( query: string, options: { source : { soundcloud : "tracks" } } & SearchOptions) : Promise<SoundCloudTrack[]>;
|
||||
export async function search( query: string, options: { source : { spotify : "album" } } & SearchOptions) : Promise<SpotifyAlbum[]>;
|
||||
export async function search( query: string, options: { source : { spotify : "playlist" } } & SearchOptions) : Promise<SpotifyPlaylist[]>;
|
||||
export async function search( query: string, options: { source : { spotify : "track" } } & SearchOptions) : Promise<SpotifyTrack[]>;
|
||||
export async function search( query: string, options: { source : { youtube : "channel" } } & SearchOptions) : Promise<YouTubeChannel[]>;
|
||||
export async function search( query: string, options: { source : { youtube : "playlist" } } & SearchOptions) : Promise<YouTubePlayList[]>;
|
||||
export async function search( query: string, options: { source : { youtube : "video" } } & SearchOptions) : Promise<YouTubeVideo[]>;
|
||||
export async function search(
|
||||
query: string,
|
||||
options: SearchOptions = {}
|
||||
@ -90,7 +114,7 @@ export async function search(
|
||||
else if (options.source.spotify) return await sp_search(query, options.source.spotify, options.limit);
|
||||
else if (options.source.soundcloud) return await so_search(query, options.source.soundcloud, options.limit);
|
||||
else if (options.source.deezer)
|
||||
return await dz_search(query, { limit: options.limit, type: options.source.deezer });
|
||||
return await dz_search(query, { limit: options.limit, type: options.source.deezer, fuzzy : options.fuzzy });
|
||||
else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');
|
||||
}
|
||||
|
||||
|
||||
5
typedoc.json
Normal file
5
typedoc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"entryPoints": ["./play-dl"],
|
||||
"plugin" : "typedoc-plugin-missing-exports",
|
||||
"out": "docs"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user