Add support for upcoming videos, adding the upcoming property to YouTubeVideo
This commit is contained in:
parent
2c7e94402c
commit
285465a1c6
@ -38,6 +38,10 @@ interface VideoOptions {
|
||||
* YouTube Video Uploaded Date
|
||||
*/
|
||||
uploadedAt?: string;
|
||||
/**
|
||||
* If the video is upcoming or a premiere that isn't currently live, this will contain the premiere date, for watch page playlists this will be true, it defaults to undefined
|
||||
*/
|
||||
upcoming?: Date | true;
|
||||
/**
|
||||
* YouTube Views
|
||||
*/
|
||||
@ -115,6 +119,10 @@ export class YouTubeVideo {
|
||||
* YouTube Video Uploaded Date
|
||||
*/
|
||||
uploadedAt?: string;
|
||||
/**
|
||||
* If the video is upcoming or a premiere that isn't currently live, this will contain the premiere date, for watch page playlists this will be true, it defaults to undefined
|
||||
*/
|
||||
upcoming?: Date | true;
|
||||
/**
|
||||
* YouTube Views
|
||||
*/
|
||||
@ -166,6 +174,7 @@ export class YouTubeVideo {
|
||||
this.durationRaw = data.duration_raw || '0:00';
|
||||
this.durationInSec = (data.duration < 0 ? 0 : data.duration) || 0;
|
||||
this.uploadedAt = data.uploadedAt || undefined;
|
||||
this.upcoming = data.upcoming;
|
||||
this.views = parseInt(data.views) || 0;
|
||||
const thumbnails = [];
|
||||
for (const thumb of data.thumbnails) {
|
||||
|
||||
@ -19,7 +19,7 @@ export interface StreamOptions {
|
||||
language?: string;
|
||||
htmldata?: boolean;
|
||||
precache?: number;
|
||||
discordPlayerCompatibility?: boolean
|
||||
discordPlayerCompatibility?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,6 +63,9 @@ export async function stream_from_info(
|
||||
info: InfoData | StreamInfoData,
|
||||
options: StreamOptions = {}
|
||||
): Promise<YouTubeStream> {
|
||||
if (!info.format || info.format.length === 0)
|
||||
throw new Error('Upcoming and premiere videos that are not currently live cannot be streamed.');
|
||||
|
||||
const final: any[] = [];
|
||||
if (
|
||||
info.LiveStreamData.isLive === true &&
|
||||
@ -87,8 +90,8 @@ export async function stream_from_info(
|
||||
final[0].codec === 'opus' && final[0].container === 'webm' ? StreamType.WebmOpus : StreamType.Arbitrary;
|
||||
await request_stream(`https://${new URL(final[0].url).host}/generate_204`);
|
||||
if (type === StreamType.WebmOpus) {
|
||||
if(!options.discordPlayerCompatibility){
|
||||
options.seek ??= 0
|
||||
if (!options.discordPlayerCompatibility) {
|
||||
options.seek ??= 0;
|
||||
if (options.seek >= info.video_details.durationInSec || options.seek < 0)
|
||||
throw new Error(`Seeking beyond limit. [ 0 - ${info.video_details.durationInSec - 1}]`);
|
||||
return new SeekStream(
|
||||
@ -99,7 +102,7 @@ export async function stream_from_info(
|
||||
info.video_details.url,
|
||||
options
|
||||
);
|
||||
} else if(options.seek) throw new Error("Can not seek with discordPlayerCompatibility set to true.")
|
||||
} else if (options.seek) throw new Error('Can not seek with discordPlayerCompatibility set to true.');
|
||||
}
|
||||
return new Stream(
|
||||
final[0].url,
|
||||
|
||||
@ -159,6 +159,7 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
|
||||
const vid = player_response.videoDetails;
|
||||
|
||||
let discretionAdvised = false;
|
||||
let upcoming = false;
|
||||
if (player_response.playabilityStatus.status !== 'OK') {
|
||||
if (player_response.playabilityStatus.status === 'CONTENT_CHECK_REQUIRED') {
|
||||
if (options.htmldata)
|
||||
@ -179,7 +180,8 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
|
||||
const updatedValues = await acceptViewerDiscretion(vid.videoId, cookieJar, body, true);
|
||||
player_response.streamingData = updatedValues.streamingData;
|
||||
initial_response.contents.twoColumnWatchNextResults.secondaryResults = updatedValues.relatedVideos;
|
||||
} else
|
||||
} else if (player_response.playabilityStatus.status === 'LIVE_STREAM_OFFLINE') upcoming = true;
|
||||
else
|
||||
throw new Error(
|
||||
`While getting info from url\n${
|
||||
player_response.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason.simpleText ??
|
||||
@ -223,6 +225,17 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
|
||||
x.metadataRowRenderer.contents[0].simpleText ?? x.metadataRowRenderer.contents[0]?.runs?.[0]?.text;
|
||||
});
|
||||
}
|
||||
let upcomingDate;
|
||||
if (upcoming) {
|
||||
if (microformat.liveBroadcastDetails.startTimestamp)
|
||||
upcomingDate = new Date(microformat.liveBroadcastDetails.startTimestamp);
|
||||
else {
|
||||
const timestamp =
|
||||
player_response.playabilityStatus.liveStreamability.liveStreamabilityRenderer.offlineSlate
|
||||
.liveStreamOfflineSlateRenderer.scheduledStartTime;
|
||||
upcomingDate = new Date(parseInt(timestamp) * 1000);
|
||||
}
|
||||
}
|
||||
const video_details = new YouTubeVideo({
|
||||
id: vid.videoId,
|
||||
title: vid.title,
|
||||
@ -230,6 +243,7 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
|
||||
duration: Number(vid.lengthSeconds),
|
||||
duration_raw: parseSeconds(vid.lengthSeconds),
|
||||
uploadedAt: microformat.publishDate,
|
||||
upcoming: upcomingDate,
|
||||
thumbnails: vid.thumbnail.thumbnails,
|
||||
channel: {
|
||||
name: vid.author,
|
||||
@ -254,8 +268,11 @@ export async function video_basic_info(url: string, options: InfoOptions = {}):
|
||||
discretionAdvised,
|
||||
music
|
||||
});
|
||||
const format = player_response.streamingData.formats ?? [];
|
||||
format.push(...(player_response.streamingData.adaptiveFormats ?? []));
|
||||
let format = [];
|
||||
if (!upcoming) {
|
||||
format.push(...(player_response.streamingData.formats ?? []));
|
||||
format.push(...(player_response.streamingData.adaptiveFormats ?? []));
|
||||
}
|
||||
const LiveStreamData = {
|
||||
isLive: video_details.live,
|
||||
dashManifestUrl: player_response.streamingData?.dashManifestUrl ?? null,
|
||||
@ -304,6 +321,7 @@ export async function video_stream_info(url: string, options: InfoOptions = {}):
|
||||
.split(/;\s*(var|const|let)\s/)[0];
|
||||
if (!player_data) throw new Error('Initial Player Response Data is undefined.');
|
||||
const player_response = JSON.parse(player_data);
|
||||
let upcoming = false;
|
||||
if (player_response.playabilityStatus.status !== 'OK') {
|
||||
if (player_response.playabilityStatus.status === 'CONTENT_CHECK_REQUIRED') {
|
||||
if (options.htmldata)
|
||||
@ -334,7 +352,8 @@ export async function video_stream_info(url: string, options: InfoOptions = {}):
|
||||
false
|
||||
);
|
||||
player_response.streamingData = updatedValues.streamingData;
|
||||
} else
|
||||
} else if (player_response.playabilityStatus.status === 'LIVE_STREAM_OFFLINE') upcoming = true;
|
||||
else
|
||||
throw new Error(
|
||||
`While getting info from url\n${
|
||||
player_response.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason.simpleText ??
|
||||
@ -348,8 +367,11 @@ export async function video_stream_info(url: string, options: InfoOptions = {}):
|
||||
url: `https://www.youtube.com/watch?v=${player_response.videoDetails.videoId}`,
|
||||
durationInSec: (duration < 0 ? 0 : duration) || 0
|
||||
};
|
||||
const format = player_response.streamingData.formats ?? [];
|
||||
format.push(...(player_response.streamingData.adaptiveFormats ?? []));
|
||||
let format = [];
|
||||
if (!upcoming) {
|
||||
format.push(...(player_response.streamingData.formats ?? []));
|
||||
format.push(...(player_response.streamingData.adaptiveFormats ?? []));
|
||||
}
|
||||
|
||||
const LiveStreamData = {
|
||||
isLive: player_response.videoDetails.isLiveContent,
|
||||
@ -414,7 +436,7 @@ export async function decipher_info<T extends InfoData | StreamInfoData>(data: T
|
||||
data.video_details.durationInSec === 0
|
||||
) {
|
||||
return data;
|
||||
} else if (data.format[0].signatureCipher || data.format[0].cipher) {
|
||||
} else if (data.format.length > 0 && (data.format[0].signatureCipher || data.format[0].cipher)) {
|
||||
data.format = await format_decipher(data.format, data.html5player);
|
||||
return data;
|
||||
} else {
|
||||
@ -495,6 +517,9 @@ export function getPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {
|
||||
duration_raw: info.lengthText?.simpleText ?? '0:00',
|
||||
thumbnails: info.thumbnail.thumbnails,
|
||||
title: info.title.runs[0].text,
|
||||
upcoming: info.upcomingEventData?.startTime
|
||||
? new Date(parseInt(info.upcomingEventData.startTime) * 1000)
|
||||
: undefined,
|
||||
channel: {
|
||||
id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined,
|
||||
name: info.shortBylineText.runs[0].text || undefined,
|
||||
@ -723,6 +748,8 @@ function getWatchPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {
|
||||
duration_raw: info.lengthText?.simpleText ?? '0:00',
|
||||
thumbnails: info.thumbnail.thumbnails,
|
||||
title: info.title.simpleText,
|
||||
upcoming:
|
||||
info.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer.style === 'UPCOMING' || undefined,
|
||||
channel: {
|
||||
id: channel_info.navigationEndpoint.browseEndpoint.browseId || undefined,
|
||||
name: channel_info.text || undefined,
|
||||
|
||||
@ -146,6 +146,9 @@ export function parseVideo(data?: any): YouTubeVideo {
|
||||
artist: Boolean(badge?.includes('artist'))
|
||||
},
|
||||
uploadedAt: data.videoRenderer.publishedTimeText?.simpleText ?? null,
|
||||
upcoming: data.videoRenderer.upcomingEventData?.startTime
|
||||
? new Date(parseInt(data.videoRenderer.upcomingEventData.startTime) * 1000)
|
||||
: undefined,
|
||||
views: data.videoRenderer.viewCountText?.simpleText?.replace(/\D/g, '') ?? 0,
|
||||
live: durationText ? false : true
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user