prettier code

This commit is contained in:
killer069 2021-12-14 15:01:10 +05:30
parent 2974efdebc
commit af5a418d09
5 changed files with 258 additions and 255 deletions

View File

@ -94,7 +94,7 @@ export class YouTubePlayList {
this.views = data.views || 0; this.views = data.views || 0;
this.link = data.link || undefined; this.link = data.link || undefined;
this.channel = new YouTubeChannel(data.channel) || undefined; this.channel = new YouTubeChannel(data.channel) || undefined;
this.thumbnail = (data.thumbnail) ? new YouTubeThumbnail(data.thumbnail) : undefined; this.thumbnail = data.thumbnail ? new YouTubeThumbnail(data.thumbnail) : undefined;
this.videos = data.videos || []; this.videos = data.videos || [];
this.__count++; this.__count++;
this.fetched_videos.set(`${this.__count}`, this.videos as YouTubeVideo[]); this.fetched_videos.set(`${this.__count}`, this.videos as YouTubeVideo[]);

View File

@ -1,14 +1,14 @@
import { IncomingMessage } from "http"; import { IncomingMessage } from 'http';
import { request_stream } from "../../Request"; import { request_stream } from '../../Request';
import { parseAudioFormats, StreamOptions, StreamType } from "../stream"; import { parseAudioFormats, StreamOptions, StreamType } from '../stream';
import { video_info } from "../utils"; import { video_info } from '../utils';
import { Timer } from "./LiveStream"; import { Timer } from './LiveStream';
import { WebmSeeker, WebmSeekerState } from "./WebmSeeker"; import { WebmSeeker, WebmSeekerState } from './WebmSeeker';
/** /**
* YouTube Stream Class for seeking audio to a timeStamp. * YouTube Stream Class for seeking audio to a timeStamp.
*/ */
export class SeekStream { export class SeekStream {
/** /**
* WebmSeeker Stream through which data passes * WebmSeeker Stream through which data passes
*/ */
@ -61,14 +61,12 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
* @param video_url YouTube video url. * @param video_url YouTube video url.
* @param options Options provided to stream function. * @param options Options provided to stream function.
*/ */
constructor( constructor(url: string, duration: number, contentLength: number, video_url: string, options: StreamOptions) {
url: string, this.stream = new WebmSeeker({
duration: number, highWaterMark: 5 * 1000 * 1000,
contentLength: number, readableObjectMode: true,
video_url: string, mode: options.seekMode
options: StreamOptions });
) {
this.stream = new WebmSeeker({ highWaterMark: 5 * 1000 * 1000, readableObjectMode : true, mode : options.seekMode });
this.url = url; this.url = url;
this.quality = options.quality as number; this.quality = options.quality as number;
this.type = StreamType.Opus; this.type = StreamType.Opus;
@ -85,18 +83,18 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
this.timer.destroy(); this.timer.destroy();
this.cleanup(); this.cleanup();
}); });
this.seek(options.seek!) this.seek(options.seek!);
} }
/** /**
* **INTERNAL Function** * **INTERNAL Function**
* *
* Uses stream functions to parse Webm Head and gets Offset byte to seek to. * Uses stream functions to parse Webm Head and gets Offset byte to seek to.
* @param sec No of seconds to seek to * @param sec No of seconds to seek to
* @returns Nothing * @returns Nothing
*/ */
private async seek(sec : number){ private async seek(sec: number) {
await new Promise(async(res) => { await new Promise(async (res) => {
if(!this.stream.headerparsed){ if (!this.stream.headerparsed) {
const stream = await request_stream(this.url, { const stream = await request_stream(this.url, {
headers: { headers: {
range: `bytes=0-1000` range: `bytes=0-1000`
@ -111,18 +109,17 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
return; return;
} }
this.request = stream this.request = stream;
stream.pipe(this.stream, { end : false }) stream.pipe(this.stream, { end: false });
stream.once('end', () => { stream.once('end', () => {
this.stream.state = WebmSeekerState.READING_DATA this.stream.state = WebmSeekerState.READING_DATA;
res('') res('');
}) });
} } else res('');
else res('') });
})
const bytes = this.stream.seek(sec) const bytes = this.stream.seek(sec);
if (bytes instanceof Error) { if (bytes instanceof Error) {
this.stream.emit('error', bytes); this.stream.emit('error', bytes);
this.bytes_count = 0; this.bytes_count = 0;
@ -131,10 +128,10 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
return; return;
} }
this.stream.seekfound = false this.stream.seekfound = false;
this.bytes_count = bytes this.bytes_count = bytes;
this.timer.reuse() this.timer.reuse();
this.loop() this.loop();
} }
/** /**
* Retry if we get 404 or 403 Errors. * Retry if we get 404 or 403 Errors.
@ -186,7 +183,7 @@ import { WebmSeeker, WebmSeekerState } from "./WebmSeeker";
return; return;
} }
this.request = stream; this.request = stream;
stream.pipe(this.stream, { end : false }) stream.pipe(this.stream, { end: false });
stream.once('error', async () => { stream.once('error', async () => {
this.cleanup(); this.cleanup();

View File

@ -1,217 +1,224 @@
import { WebmElements, WebmHeader } from 'play-audio' import { WebmElements, WebmHeader } from 'play-audio';
import { Duplex, DuplexOptions } from 'stream' import { Duplex, DuplexOptions } from 'stream';
enum DataType { master, string, uint, binary, float } enum DataType {
master,
string,
uint,
binary,
float
}
export enum WebmSeekerState{ export enum WebmSeekerState {
READING_HEAD = 'READING_HEAD', READING_HEAD = 'READING_HEAD',
READING_DATA = 'READING_DATA', READING_DATA = 'READING_DATA'
} }
interface WebmSeekerOptions extends DuplexOptions{ interface WebmSeekerOptions extends DuplexOptions {
mode? : "precise" | "granular" mode?: 'precise' | 'granular';
} }
export class WebmSeeker extends Duplex{ export class WebmSeeker extends Duplex {
remaining? : Buffer remaining?: Buffer;
state : WebmSeekerState state: WebmSeekerState;
mode : "precise" | "granular" mode: 'precise' | 'granular';
chunk? : Buffer chunk?: Buffer;
cursor : number cursor: number;
header : WebmHeader header: WebmHeader;
headfound : boolean headfound: boolean;
headerparsed : boolean headerparsed: boolean;
time_left : number time_left: number;
seekfound : boolean seekfound: boolean;
private data_size : number private data_size: number;
private data_length : number private data_length: number;
constructor(options : WebmSeekerOptions){ constructor(options: WebmSeekerOptions) {
super(options) super(options);
this.state = WebmSeekerState.READING_HEAD this.state = WebmSeekerState.READING_HEAD;
this.cursor = 0 this.cursor = 0;
this.header = new WebmHeader() this.header = new WebmHeader();
this.headfound = false this.headfound = false;
this.time_left = 0 this.time_left = 0;
this.headerparsed = false this.headerparsed = false;
this.seekfound = false this.seekfound = false;
this.data_length = 0 this.data_length = 0;
this.mode = options.mode || "granular" this.mode = options.mode || 'granular';
this.data_size = 0 this.data_size = 0;
} }
private get vint_length(): number{ private get vint_length(): number {
let i = 0; let i = 0;
for (; i < 8; i++){ for (; i < 8; i++) {
if ((1 << (7 - i)) & this.chunk![this.cursor]) if ((1 << (7 - i)) & this.chunk![this.cursor]) break;
break; }
} return ++i;
return ++i;
} }
private get vint_value(): boolean { private get vint_value(): boolean {
if (!this.chunk) return false if (!this.chunk) return false;
const length = this.vint_length const length = this.vint_length;
if(this.chunk.length < this.cursor + length) return false if (this.chunk.length < this.cursor + length) return false;
let value = this.chunk[this.cursor] & ((1 << (8 - length)) - 1) let value = this.chunk[this.cursor] & ((1 << (8 - length)) - 1);
for (let i = this.cursor + 1; i < this.cursor + length; i++) value = (value << 8) + this.chunk[i]; for (let i = this.cursor + 1; i < this.cursor + length; i++) value = (value << 8) + this.chunk[i];
this.data_size = length this.data_size = length;
this.data_length = value this.data_length = value;
return true return true;
} }
cleanup(){ cleanup() {
this.cursor = 0 this.cursor = 0;
this.chunk = undefined this.chunk = undefined;
this.remaining = undefined this.remaining = undefined;
} }
_read() {} _read() {}
seek(sec : number): Error | number{ seek(sec: number): Error | number {
let position = 0 let position = 0;
let time = (Math.floor(sec / 10) * 10) let time = Math.floor(sec / 10) * 10;
this.time_left = (sec - time) * 1000 || 0 this.time_left = (sec - time) * 1000 || 0;
if (!this.header.segment.cues) return new Error("Failed to Parse Cues") if (!this.header.segment.cues) return new Error('Failed to Parse Cues');
for(const data of this.header.segment.cues){ for (const data of this.header.segment.cues) {
if(Math.floor(data.time as number / 1000) === time) { if (Math.floor((data.time as number) / 1000) === time) {
position = data.position as number position = data.position as number;
break; break;
} } else continue;
else continue;
} }
if(position === 0) return Error("Failed to find Cluster Position") if (position === 0) return Error('Failed to find Cluster Position');
else return position else return position;
} }
_write(chunk: Buffer, _: BufferEncoding, callback: (error?: Error | null) => void): void { _write(chunk: Buffer, _: BufferEncoding, callback: (error?: Error | null) => void): void {
if (this.remaining) { if (this.remaining) {
this.chunk = Buffer.concat([this.remaining, chunk]) this.chunk = Buffer.concat([this.remaining, chunk]);
this.remaining = undefined this.remaining = undefined;
} } else this.chunk = chunk;
else this.chunk = chunk
let err : Error | undefined; let err: Error | undefined;
if(this.state === WebmSeekerState.READING_HEAD) err = this.readHead() if (this.state === WebmSeekerState.READING_HEAD) err = this.readHead();
else if(!this.seekfound) err = this.getClosetCluster() else if (!this.seekfound) err = this.getClosetCluster();
else err = this.readTag() else err = this.readTag();
if(err) callback(err) if (err) callback(err);
else callback() else callback();
} }
private readHead(): Error | undefined{ private readHead(): Error | undefined {
if (!this.chunk) return new Error("Chunk is missing") if (!this.chunk) return new Error('Chunk is missing');
while(this.chunk.length > this.cursor ){
const oldCursor = this.cursor
const id = this.vint_length
if(this.chunk.length < this.cursor + id) break;
const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex')) while (this.chunk.length > this.cursor) {
this.cursor += id const oldCursor = this.cursor;
const vint = this.vint_value const id = this.vint_length;
if (this.chunk.length < this.cursor + id) break;
if(!vint) { const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex'));
this.cursor = oldCursor this.cursor += id;
break; const vint = this.vint_value;
}
if(!ebmlID){
this.cursor += this.data_size + this.data_length
continue;
}
if(!this.headfound){ if (!vint) {
if(ebmlID.name === "ebml") this.headfound = true
else return new Error("Failed to find EBML ID at start of stream.")
}
const data = this.chunk.slice(this.cursor + this.data_size, this.cursor + this.data_size + this.data_length)
const parse = this.header.parse(ebmlID, data)
if(parse instanceof Error) return parse
if(ebmlID.type === DataType.master) {
this.cursor += this.data_size
continue;
}
if(this.chunk.length < this.cursor + this.data_size + this.data_length) {
this.cursor = oldCursor; this.cursor = oldCursor;
break; break;
} }
else this.cursor += this.data_size + this.data_length if (!ebmlID) {
this.cursor += this.data_size + this.data_length;
continue;
}
if (!this.headfound) {
if (ebmlID.name === 'ebml') this.headfound = true;
else return new Error('Failed to find EBML ID at start of stream.');
}
const data = this.chunk.slice(
this.cursor + this.data_size,
this.cursor + this.data_size + this.data_length
);
const parse = this.header.parse(ebmlID, data);
if (parse instanceof Error) return parse;
if (ebmlID.type === DataType.master) {
this.cursor += this.data_size;
continue;
}
if (this.chunk.length < this.cursor + this.data_size + this.data_length) {
this.cursor = oldCursor;
break;
} else this.cursor += this.data_size + this.data_length;
} }
this.remaining = this.chunk.slice(this.cursor) this.remaining = this.chunk.slice(this.cursor);
this.cursor = 0 this.cursor = 0;
} }
private readTag(): Error | undefined{ private readTag(): Error | undefined {
if (!this.chunk) return new Error("Chunk is missing") if (!this.chunk) return new Error('Chunk is missing');
while(this.chunk.length > this.cursor ){
const oldCursor = this.cursor
const id = this.vint_length
if(this.chunk.length < this.cursor + id) break;
const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex')) while (this.chunk.length > this.cursor) {
this.cursor += id const oldCursor = this.cursor;
const vint = this.vint_value const id = this.vint_length;
if (this.chunk.length < this.cursor + id) break;
if(!vint) { const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex'));
this.cursor = oldCursor this.cursor += id;
break; const vint = this.vint_value;
}
if(!ebmlID){
this.cursor += this.data_size + this.data_length
continue;
}
const data = this.chunk.slice(this.cursor + this.data_size, this.cursor + this.data_size + this.data_length) if (!vint) {
const parse = this.header.parse(ebmlID, data)
if(parse instanceof Error) return parse
if(ebmlID.type === DataType.master) {
this.cursor += this.data_size
continue;
}
if(this.chunk.length < this.cursor + this.data_size + this.data_length) {
this.cursor = oldCursor; this.cursor = oldCursor;
break; break;
} }
else this.cursor += this.data_size + this.data_length if (!ebmlID) {
this.cursor += this.data_size + this.data_length;
continue;
}
if(ebmlID.name === 'simpleBlock'){ const data = this.chunk.slice(
if(this.time_left !== 0 && this.mode === "precise"){ this.cursor + this.data_size,
if(data.readUInt16BE(1) === this.time_left) this.time_left = 0 this.cursor + this.data_size + this.data_length
);
const parse = this.header.parse(ebmlID, data);
if (parse instanceof Error) return parse;
if (ebmlID.type === DataType.master) {
this.cursor += this.data_size;
continue;
}
if (this.chunk.length < this.cursor + this.data_size + this.data_length) {
this.cursor = oldCursor;
break;
} else this.cursor += this.data_size + this.data_length;
if (ebmlID.name === 'simpleBlock') {
if (this.time_left !== 0 && this.mode === 'precise') {
if (data.readUInt16BE(1) === this.time_left) this.time_left = 0;
else continue; else continue;
} }
const track = this.header.segment.tracks![this.header.audioTrack] const track = this.header.segment.tracks![this.header.audioTrack];
if(!track || track.trackType !== 2) return new Error("No audio Track in this webm file.") if (!track || track.trackType !== 2) return new Error('No audio Track in this webm file.');
if((data[0] & 0xf) === track.trackNumber) this.push(data.slice(4)) if ((data[0] & 0xf) === track.trackNumber) this.push(data.slice(4));
} }
} }
this.remaining = this.chunk.slice(this.cursor) this.remaining = this.chunk.slice(this.cursor);
this.cursor = 0 this.cursor = 0;
} }
private getClosetCluster(): Error | undefined{ private getClosetCluster(): Error | undefined {
if(!this.chunk) return new Error("Chunk is missing") if (!this.chunk) return new Error('Chunk is missing');
const count = this.chunk.indexOf('1f43b675', 0, 'hex') const count = this.chunk.indexOf('1f43b675', 0, 'hex');
if(count === -1) throw new Error("Failed to find nearest Cluster.") if (count === -1) throw new Error('Failed to find nearest Cluster.');
else this.chunk = this.chunk.slice(count) else this.chunk = this.chunk.slice(count);
this.seekfound = true this.seekfound = true;
return this.readTag() return this.readTag();
} }
private parseEbmlID(ebmlID : string){ private parseEbmlID(ebmlID: string) {
if(Object.keys(WebmElements).includes(ebmlID)) return WebmElements[ebmlID] if (Object.keys(WebmElements).includes(ebmlID)) return WebmElements[ebmlID];
else return false else return false;
} }
_destroy(error : Error | null, callback : (error : Error | null) => void) : void { _destroy(error: Error | null, callback: (error: Error | null) => void): void {
this.cleanup() this.cleanup();
callback(error); callback(error);
} }
@ -219,4 +226,4 @@ export class WebmSeeker extends Duplex{
this.cleanup(); this.cleanup();
callback(); callback();
} }
} }

View File

@ -12,8 +12,8 @@ export enum StreamType {
} }
export interface StreamOptions { export interface StreamOptions {
seekMode? : "precise" | "granular" seekMode?: 'precise' | 'granular';
seek? : number seek?: number;
quality?: number; quality?: number;
htmldata?: boolean; htmldata?: boolean;
} }
@ -80,25 +80,25 @@ export async function stream_from_info(
else final.push(info.format[info.format.length - 1]); else final.push(info.format[info.format.length - 1]);
let type: StreamType = let type: StreamType =
final[0].codec === 'opus' && final[0].container === 'webm' ? StreamType.WebmOpus : StreamType.Arbitrary; final[0].codec === 'opus' && final[0].container === 'webm' ? StreamType.WebmOpus : StreamType.Arbitrary;
if(options.seek){ if (options.seek) {
if(type === StreamType.WebmOpus) { if (type === StreamType.WebmOpus) {
if(options.seek >= info.video_details.durationInSec) throw new Error(`Seeking beyond limit. [0 - ${info.video_details.durationInSec - 1}]`) if (options.seek >= info.video_details.durationInSec || options.seek <= 0)
throw new Error(`Seeking beyond limit. [ 1 - ${info.video_details.durationInSec - 1}]`);
return new SeekStream( return new SeekStream(
final[0].url, final[0].url,
info.video_details.durationInSec, info.video_details.durationInSec,
Number(final[0].contentLength), Number(final[0].contentLength),
info.video_details.url, info.video_details.url,
options options
) );
} } else throw new Error('Seek is only supported in Webm Opus Files.');
else throw new Error("Seek is only supported in Webm Opus Files.") } else
} return new Stream(
else return new Stream( final[0].url,
final[0].url, type,
type, info.video_details.durationInSec,
info.video_details.durationInSec, Number(final[0].contentLength),
Number(final[0].contentLength), info.video_details.url,
info.video_details.url, options
options );
);
} }

View File

@ -327,12 +327,12 @@ export async function decipher_info<T extends InfoData | StreamInfoData>(data: T
*/ */
export async function playlist_info(url: string, options: PlaylistOptions = {}): Promise<YouTubePlayList> { export async function playlist_info(url: string, options: PlaylistOptions = {}): Promise<YouTubePlayList> {
if (!url || typeof url !== 'string') throw new Error(`Expected playlist url, received ${typeof url}!`); if (!url || typeof url !== 'string') throw new Error(`Expected playlist url, received ${typeof url}!`);
if (!url.startsWith('https')) url = `https://www.youtube.com/playlist?list=${url}` if (!url.startsWith('https')) url = `https://www.youtube.com/playlist?list=${url}`;
if (url.indexOf('list=') === -1 ) throw new Error('This is not a Playlist URL'); if (url.indexOf('list=') === -1) throw new Error('This is not a Playlist URL');
if(yt_validate(url) === 'playlist') { if (yt_validate(url) === 'playlist') {
const id = extractID(url) const id = extractID(url);
url = `https://www.youtube.com/playlist?list=${id}` url = `https://www.youtube.com/playlist?list=${id}`;
} }
const body = await request(url, { const body = await request(url, {
@ -353,10 +353,9 @@ export async function playlist_info(url: string, options: PlaylistOptions = {}):
throw new Error(`While parsing playlist url\n${response.alerts[0].alertRenderer.text.runs[0].text}`); throw new Error(`While parsing playlist url\n${response.alerts[0].alertRenderer.text.runs[0].text}`);
else throw new Error('While parsing playlist url\nUnknown Playlist Error'); else throw new Error('While parsing playlist url\nUnknown Playlist Error');
} }
if(url.indexOf('watch?v=') !== -1){ if (url.indexOf('watch?v=') !== -1) {
return getWatchPlaylist(response, body) return getWatchPlaylist(response, body);
} } else return getNormalPlaylist(response, body);
else return getNormalPlaylist(response, body)
} }
/** /**
* Function to parse Playlist from YouTube search * Function to parse Playlist from YouTube search
@ -377,7 +376,7 @@ export function getPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {
id: info.videoId, id: info.videoId,
duration: parseInt(info.lengthSeconds) || 0, duration: parseInt(info.lengthSeconds) || 0,
duration_raw: info.lengthText?.simpleText ?? '0:00', duration_raw: info.lengthText?.simpleText ?? '0:00',
thumbnails : info.thumbnail.thumbnails, thumbnails: info.thumbnail.thumbnails,
title: info.title.runs[0].text, title: info.title.runs[0].text,
channel: { channel: {
id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined, id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined,
@ -403,19 +402,18 @@ export function getContinuationToken(data: any): string {
.continuationEndpoint?.continuationCommand?.token; .continuationEndpoint?.continuationCommand?.token;
} }
function getWatchPlaylist(response: any, body: any): YouTubePlayList {
const playlist_details = response.contents.twoColumnWatchNextResults.playlist.playlist;
function getWatchPlaylist(response : any, body : any) : YouTubePlayList{ const videos = getWatchPlaylistVideos(playlist_details.contents);
const playlist_details = response.contents.twoColumnWatchNextResults.playlist.playlist
const videos = getWatchPlaylistVideos(playlist_details.contents)
const API_KEY = const API_KEY =
body.split('INNERTUBE_API_KEY":"')[1]?.split('"')[0] ?? body.split('INNERTUBE_API_KEY":"')[1]?.split('"')[0] ??
body.split('innertubeApiKey":"')[1]?.split('"')[0] ?? body.split('innertubeApiKey":"')[1]?.split('"')[0] ??
DEFAULT_API_KEY; DEFAULT_API_KEY;
const videoCount = playlist_details.totalVideos const videoCount = playlist_details.totalVideos;
const channel = playlist_details.shortBylineText?.runs?.[0] const channel = playlist_details.shortBylineText?.runs?.[0];
const badge = playlist_details.badges?.[0]?.metadataBadgeRenderer?.style.toLowerCase() const badge = playlist_details.badges?.[0]?.metadataBadgeRenderer?.style.toLowerCase();
return new YouTubePlayList({ return new YouTubePlayList({
continuation: { continuation: {
@ -426,12 +424,12 @@ function getWatchPlaylist(response : any, body : any) : YouTubePlayList{
body.split('"innertube_context_client_version":"')[1]?.split('"')[0] ?? body.split('"innertube_context_client_version":"')[1]?.split('"')[0] ??
'<some version>' '<some version>'
}, },
id : playlist_details.playlistId || '', id: playlist_details.playlistId || '',
title : playlist_details.title || '', title: playlist_details.title || '',
videoCount : parseInt(videoCount) || 0, videoCount: parseInt(videoCount) || 0,
videos : videos, videos: videos,
url : `https://www.youtube.com/playlist?list=${playlist_details.playlistId}`, url: `https://www.youtube.com/playlist?list=${playlist_details.playlistId}`,
channel : { channel: {
id: channel?.navigationEndpoint?.browseEndpoint?.browseId || null, id: channel?.navigationEndpoint?.browseEndpoint?.browseId || null,
name: channel?.text || null, name: channel?.text || null,
url: `https://www.youtube.com${ url: `https://www.youtube.com${
@ -441,12 +439,13 @@ function getWatchPlaylist(response : any, body : any) : YouTubePlayList{
verified: Boolean(badge?.includes('verified')), verified: Boolean(badge?.includes('verified')),
artist: Boolean(badge?.includes('artist')) artist: Boolean(badge?.includes('artist'))
} }
}) });
} }
function getNormalPlaylist(response : any, body : any): YouTubePlayList{ function getNormalPlaylist(response: any, body: any): YouTubePlayList {
const json_data =
const json_data = response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer.contents; response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0]
.itemSectionRenderer.contents[0].playlistVideoListRenderer.contents;
const playlist_details = response.sidebar.playlistSidebarRenderer.items; const playlist_details = response.sidebar.playlistSidebarRenderer.items;
const API_KEY = const API_KEY =
@ -507,21 +506,21 @@ function getNormalPlaylist(response : any, body : any): YouTubePlayList{
return res; return res;
} }
function getWatchPlaylistVideos(data : any, limit = Infinity): YouTubeVideo[] { function getWatchPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {
const videos: YouTubeVideo[] = [] const videos: YouTubeVideo[] = [];
for(let i = 0; i < data.length ; i++) { for (let i = 0; i < data.length; i++) {
if(limit === videos.length) break; if (limit === videos.length) break;
const info = data[i].playlistPanelVideoRenderer; const info = data[i].playlistPanelVideoRenderer;
if(!info || !info.shortBylineText) continue; if (!info || !info.shortBylineText) continue;
const channel_info = info.shortBylineText.runs[0] const channel_info = info.shortBylineText.runs[0];
videos.push( videos.push(
new YouTubeVideo({ new YouTubeVideo({
id: info.videoId, id: info.videoId,
duration: parseDuration(info.lengthText?.simpleText) || 0, duration: parseDuration(info.lengthText?.simpleText) || 0,
duration_raw: info.lengthText?.simpleText ?? '0:00', duration_raw: info.lengthText?.simpleText ?? '0:00',
thumbnails : info.thumbnail.thumbnails, thumbnails: info.thumbnail.thumbnails,
title: info.title.simpleText, title: info.title.simpleText,
channel: { channel: {
id: channel_info.navigationEndpoint.browseEndpoint.browseId || undefined, id: channel_info.navigationEndpoint.browseEndpoint.browseId || undefined,
@ -536,21 +535,21 @@ function getWatchPlaylistVideos(data : any, limit = Infinity): YouTubeVideo[] {
); );
} }
return videos return videos;
} }
function parseDuration(text : string): number{ function parseDuration(text: string): number {
if(!text) return 0 if (!text) return 0;
const split = text.split(':') const split = text.split(':');
switch (split.length){ switch (split.length) {
case 2: case 2:
return (parseInt(split[0]) * 60) + (parseInt(split[1])) return parseInt(split[0]) * 60 + parseInt(split[1]);
case 3:
return (parseInt(split[0]) * 60 * 60) + (parseInt(split[1]) * 60) + (parseInt(split[2]))
default : case 3:
return 0 return parseInt(split[0]) * 60 * 60 + parseInt(split[1]) * 60 + parseInt(split[2]);
default:
return 0;
} }
} }