Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(yt): add support for movie items and trailers #349

Merged
merged 1 commit into from
Mar 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ Retrieves video info, including playback data and even layout elements such as m
- `<info>#getLiveChat()`
- Returns a LiveChat instance.

- `<info>#getTrailerInfo()`
- Returns trailer info in a new `VideoInfo` instance, or `null` if none. Typically available for non-purchased movies or films.

- `<info>#chooseFormat(options)`
- Used to choose streaming data formats.

Expand All @@ -324,6 +327,9 @@ Retrieves video info, including playback data and even layout elements such as m
- `<info>#autoplay_video_endpoint`
- Returns the endpoint of the video for Autoplay.

- `<info>#has_trailer`
- Checks if trailer is available.

- `<info>#page`
- Returns original InnerTube response (sanitized).

Expand Down
34 changes: 34 additions & 0 deletions src/parser/classes/GridMovie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Parser from '../index.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import { YTNode } from '../helpers.js';
import MetadataBadge from './MetadataBadge.js';

class GridMovie extends YTNode {
static type = 'GridMovie';

id: string;
title: Text;
thumbnails: Thumbnail[];
duration: Text | null;
endpoint: NavigationEndpoint;
badges: MetadataBadge[];
metadata: Text;
thumbnail_overlays;

constructor(data: any) {
super();
const length_alt = data.thumbnailOverlays.find((overlay: any) => overlay.hasOwnProperty('thumbnailOverlayTimeStatusRenderer'))?.thumbnailOverlayTimeStatusRenderer;
LuanRT marked this conversation as resolved.
Show resolved Hide resolved
this.id = data.videoId;
this.title = new Text(data.title);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.duration = data.lengthText ? new Text(data.lengthText) : length_alt?.text ? new Text(length_alt.text) : null;
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.badges = Parser.parseArray<MetadataBadge>(data.badges, MetadataBadge);
this.metadata = new Text(data.metadata);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
}
}

export default GridMovie;
25 changes: 25 additions & 0 deletions src/parser/classes/HorizontalMovieList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import Button from './Button.js';

class HorizontalMovieList extends YTNode {
static type = 'HorizontalMovieList';

items;
previous_button: Button | null;
next_button: Button | null;

constructor(data: any) {
super();
this.items = Parser.parseArray(data.items);
this.previous_button = Parser.parseItem<Button>(data.previousButton, Button);
this.next_button = Parser.parseItem<Button>(data.nextButton, Button);
}

// XXX: alias for consistency
get contents() {
return this.items;
}
}

export default HorizontalMovieList;
32 changes: 32 additions & 0 deletions src/parser/classes/PlayerLegacyDesktopYpcTrailer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { YTNode } from '../helpers.js';
import { Parser, RawNode } from '../index.js';
import YpcTrailer from './YpcTrailer.js';

class PlayerLegacyDesktopYpcTrailer extends YTNode {
static type = 'PlayerLegacyDesktopYpcTrailer';

video_id: string;
title: string;
thumbnail: string;
offer_headline: string;
offer_description: string;
offer_id: string;
offer_button_text: string;
video_message: string;
trailer: YpcTrailer | null;

constructor(data: RawNode) {
super();
this.video_id = data.trailerVideoId;
this.title = data.itemTitle;
this.thumbnail = data.itemThumbnail;
this.offer_headline = data.offerHeadline;
this.offer_description = data.offerDescription;
this.offer_id = data.offerId;
this.offer_button_text = data.offerButtonText;
this.video_message = data.fullVideoMessage;
this.trailer = Parser.parseItem<YpcTrailer>(data.ypcTrailer, YpcTrailer);
}
}

export default PlayerLegacyDesktopYpcTrailer;
17 changes: 17 additions & 0 deletions src/parser/classes/YpcTrailer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { YTNode } from '../helpers.js';
import { RawNode } from '../index.js';

class YpcTrailer extends YTNode {
static type = 'YpcTrailer';

video_message: string;
player_response;

constructor(data: RawNode) {
super();
this.video_message = data.fullVideoMessage;
this.player_response = data.unserializedPlayerResponse;
}
}

export default YpcTrailer;
12 changes: 12 additions & 0 deletions src/parser/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ import { default as GridChannel } from './classes/GridChannel.js';
export { GridChannel };
import { default as GridHeader } from './classes/GridHeader.js';
export { GridHeader };
import { default as GridMovie } from './classes/GridMovie.js';
export { GridMovie };
import { default as GridPlaylist } from './classes/GridPlaylist.js';
export { GridPlaylist };
import { default as GridVideo } from './classes/GridVideo.js';
Expand Down Expand Up @@ -226,6 +228,8 @@ import { default as HorizontalCardList } from './classes/HorizontalCardList.js';
export { HorizontalCardList };
import { default as HorizontalList } from './classes/HorizontalList.js';
export { HorizontalList };
import { default as HorizontalMovieList } from './classes/HorizontalMovieList.js';
export { HorizontalMovieList };
import { default as IconLink } from './classes/IconLink.js';
export { IconLink };
import { default as InteractiveTabbedHeader } from './classes/InteractiveTabbedHeader.js';
Expand Down Expand Up @@ -452,6 +456,8 @@ import { default as PlayerErrorMessage } from './classes/PlayerErrorMessage.js';
export { PlayerErrorMessage };
import { default as PlayerLegacyDesktopYpcOffer } from './classes/PlayerLegacyDesktopYpcOffer.js';
export { PlayerLegacyDesktopYpcOffer };
import { default as PlayerLegacyDesktopYpcTrailer } from './classes/PlayerLegacyDesktopYpcTrailer.js';
export { PlayerLegacyDesktopYpcTrailer };
import { default as PlayerLiveStoryboardSpec } from './classes/PlayerLiveStoryboardSpec.js';
export { PlayerLiveStoryboardSpec };
import { default as PlayerMicroformat } from './classes/PlayerMicroformat.js';
Expand Down Expand Up @@ -674,6 +680,8 @@ import { default as WatchNextEndScreen } from './classes/WatchNextEndScreen.js';
export { WatchNextEndScreen };
import { default as WatchNextTabbedResults } from './classes/WatchNextTabbedResults.js';
export { WatchNextTabbedResults };
import { default as YpcTrailer } from './classes/YpcTrailer.js';
export { YpcTrailer };
import { default as AnchoredSection } from './classes/ytkids/AnchoredSection.js';
export { AnchoredSection };
import { default as KidsCategoriesHeader } from './classes/ytkids/KidsCategoriesHeader.js';
Expand Down Expand Up @@ -780,6 +788,7 @@ const map: Record<string, YTNodeConstructor> = {
Grid,
GridChannel,
GridHeader,
GridMovie,
GridPlaylist,
GridVideo,
GuideCollapsibleEntry,
Expand All @@ -796,6 +805,7 @@ const map: Record<string, YTNodeConstructor> = {
HistorySuggestion,
HorizontalCardList,
HorizontalList,
HorizontalMovieList,
IconLink,
InteractiveTabbedHeader,
ItemSection,
Expand Down Expand Up @@ -904,6 +914,7 @@ const map: Record<string, YTNodeConstructor> = {
PlayerCaptionsTracklist,
PlayerErrorMessage,
PlayerLegacyDesktopYpcOffer,
PlayerLegacyDesktopYpcTrailer,
PlayerLiveStoryboardSpec,
PlayerMicroformat,
PlayerOverlay,
Expand Down Expand Up @@ -1015,6 +1026,7 @@ const map: Record<string, YTNodeConstructor> = {
WatchCardSectionSequence,
WatchNextEndScreen,
WatchNextTabbedResults,
YpcTrailer,
AnchoredSection,
KidsCategoriesHeader,
KidsCategoryTab,
Expand Down
22 changes: 22 additions & 0 deletions src/parser/youtube/VideoInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import VideoPrimaryInfo from '../classes/VideoPrimaryInfo.js';
import VideoSecondaryInfo from '../classes/VideoSecondaryInfo.js';
import LiveChatWrap from './LiveChat.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
import PlayerLegacyDesktopYpcTrailer from '../classes/PlayerLegacyDesktopYpcTrailer.js';

import type CardCollection from '../classes/CardCollection.js';
import type Endscreen from '../classes/Endscreen.js';
Expand Down Expand Up @@ -334,6 +335,20 @@ class VideoInfo {
return new LiveChatWrap(this);
}

/**
* Retrieves trailer info if available (typically for non-purchased movies or films).
* @returns `VideoInfo` for the trailer, or `null` if none.
*/
getTrailerInfo(): VideoInfo | null {
if (this.has_trailer) {
const player_response = this.playability_status.error_screen?.as(PlayerLegacyDesktopYpcTrailer).trailer?.player_response;
if (player_response) {
return new VideoInfo([ { data: player_response } as ApiResponse ], this.#actions, this.#player, this.#cpn);
}
}
return null;
}

/**
* Selects the format that best matches the given options.
* @param options - Options
Expand Down Expand Up @@ -395,6 +410,13 @@ class VideoInfo {
return this.autoplay?.sets?.[0]?.autoplay_video || null;
}

/**
* Checks if trailer is available.
*/
get has_trailer(): boolean {
return !!this.playability_status.error_screen?.is(PlayerLegacyDesktopYpcTrailer);
}

/**
* Get songs used in the video.
*/
Expand Down