Skip to content

Commit

Permalink
fix(VideoInfo.ts): reimplement get music_tracks (#409)
Browse files Browse the repository at this point in the history
* fix(VideoInfo.ts): reimplement `get music_tracks`

- Add parser classes to parse needed data
  - Add `CarouselLockup`
  - Add `EngagementPanelSectionList`
  - Add `InfoRow`
  - Add `StructuredDescriptionContent`
  - Add `VideoDescriptionMusicSection`
  - Add `VideoDescriptionHeader`
  - Add `Factoid`
  - Add `ExpandableVideoDescriptionBody`
  - Add `AdsEngagementPanelContent`
- Add `engagement_panels` to raw and parsed next responses
- Add `engagement_panels` parsing code to `parser.ts`

* Check for song inside of video_lockup first before checking info_rows

* Add support for pulling artist ids out of music_tracks

- Add support for WRITERS InfoRow
- Check for video id inside of naviagation endpoint on info_row metadata

* Add `AdsEngagementPanelContent` to ignore list

* Switch `map => parseItem` to `parseArray`

* Use `Text` && `NavigationEndpoint`

* Replace `String` with `Text` in `ExpandableVideoDescriptionBody`
  • Loading branch information
MarmadileManteater authored Jun 28, 2023
1 parent a11e596 commit e434bb2
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 36 deletions.
20 changes: 20 additions & 0 deletions src/parser/classes/CarouselLockup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type ObservedArray, YTNode } from '../helpers.js';
import InfoRow from './InfoRow.js';
import Parser, { type RawNode } from '../index.js';
import CompactVideo from './CompactVideo.js';

export default class CarouselLockup extends YTNode {
static type = 'CarouselLockup';

info_rows: ObservedArray<InfoRow>;
video_lockup?: CompactVideo;

constructor(data: RawNode) {
super();
this.info_rows = Parser.parseArray(data.infoRows, InfoRow);
const video_lockup = Parser.parseItem(data.videoLockup, CompactVideo);
if (video_lockup != null) {
this.video_lockup = video_lockup;
}
}
}
20 changes: 20 additions & 0 deletions src/parser/classes/EngagementPanelSectionList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { YTNode } from '../helpers.js';
import Parser, { type RawNode } from '../index.js';
import ContinuationItem from './ContinuationItem.js';
import SectionList from './SectionList.js';
import StructuredDescriptionContent from './StructuredDescriptionContent.js';

export default class EngagementPanelSectionList extends YTNode {
static type = 'EngagementPanelSectionList';

target_id: String;
content?: SectionList|ContinuationItem|StructuredDescriptionContent;
constructor(data: RawNode) {
super();
this.target_id = data.targetId;
const content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent ]);
if (content !== null) {
this.content = content;
}
}
}
22 changes: 22 additions & 0 deletions src/parser/classes/ExpandableVideoDescriptionBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { YTNode } from '../helpers.js';
import { type RawNode } from '../index.js';
import { Text } from '../misc.js';

export default class ExpandableVideoDescriptionBody extends YTNode {
static type = 'ExpandableVideoDescriptionBody';

show_more_text: Text;
show_less_text: Text;
attributed_description_body_text: {
content: String
};

constructor(data: RawNode) {
super();
this.show_more_text = new Text(data.showMoreText);
this.show_less_text = new Text(data.showLessText);
this.attributed_description_body_text = {
content: data.attributedDescriptionBodyText.content
};
}
}
17 changes: 17 additions & 0 deletions src/parser/classes/Factoid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { YTNode } from '../helpers.js';
import { type RawNode } from '../index.js';
import { Text } from '../misc.js';

export default class Factoid extends YTNode {
static type = 'Factoid';
label: Text;
value: Text;
accessibility_text: String;

constructor(data: RawNode) {
super();
this.label = new Text(data.label);
this.value = new Text(data.value);
this.accessibility_text = data.accessibilityText;
}
}
38 changes: 38 additions & 0 deletions src/parser/classes/InfoRow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { YTNode } from '../helpers.js';
import Parser, { type RawNode } from '../index.js';
import { Text } from '../misc.js';
import NavigationEndpoint from './NavigationEndpoint.js';

export default class InfoRow extends YTNode {
static type = 'InfoRow';
metadata_text?: Text;
metadata_endpoint?: NavigationEndpoint;
info_row_expand_status_key: String;
title: Text;

constructor(data: RawNode) {
super();
if ('defaultMetadata' in data && 'runs' in data.defaultMetadata) {
const runs = data.defaultMetadata.runs;
if (runs.length > 0) {
const run = runs[0];
this.metadata_text = run?.text;
if ('navigationEndpoint' in run) {
this.metadata_endpoint = Parser.parseItem({ navigationEndpoint: run.navigationEndpoint }, NavigationEndpoint) || undefined;
}
}
}
if ('expandedMetadata' in data && 'runs' in data.expandedMetadata) {
this.metadata_text = new Text(data.expandedMetadata);
}
if (this.metadata_text === undefined) {
this.metadata_text = data.expandedMetadata?.simpleText
? new Text(data.expandedMetadata)
: data.defaultMetadata?.simpleText
? new Text(data.defaultMetadata)
: undefined;
}
this.info_row_expand_status_key = data.infoRowExpandStatusKey;
this.title = new Text(data.title);
}
}
16 changes: 16 additions & 0 deletions src/parser/classes/StructuredDescriptionContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type ObservedArray, YTNode } from '../helpers.js';
import Parser, { type RawNode } from '../index.js';
import ExpandableVideoDescriptionBody from './ExpandableVideoDescriptionBody.js';
import VideoDescriptionHeader from './VideoDescriptionHeader.js';
import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.js';

export default class StructuredDescriptionContent extends YTNode {
static type = 'StructuredDescriptionContent';

items: ObservedArray<VideoDescriptionHeader|ExpandableVideoDescriptionBody|VideoDescriptionMusicSection>;

constructor(data: RawNode) {
super();
this.items = Parser.parseArray(data.items, [ VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection ]);
}
}
29 changes: 29 additions & 0 deletions src/parser/classes/VideoDescriptionHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { type ObservedArray, YTNode } from '../helpers.js';
import Parser, { type RawNode } from '../index.js';
import { Text } from '../misc.js';
import type { Thumbnail } from '../misc.js';
import Factoid from './Factoid.js';
import NavigationEndpoint from './NavigationEndpoint.js';

export default class VideoDescriptionHeader extends YTNode {
static type = 'VideoDescriptionHeader';

channel: Text;
channel_navigation_endpoint?: NavigationEndpoint;
channel_thumbnails: String[];
factoids: ObservedArray<Factoid>;
publish_date: Text;
title: Text;
views: Text;

constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.channel = new Text(data.channel);
this.channel_navigation_endpoint = Parser.parseItem(data.channelNavigationEndpoint, NavigationEndpoint) || undefined;
this.channel_thumbnails = data.channelThumbnail.thumbnails.map((thumbnail: Thumbnail) => thumbnail.url);
this.publish_date = new Text(data.publishDate);
this.views = new Text(data.views);
this.factoids = Parser.parseArray(data.factoid, Factoid);
}
}
16 changes: 16 additions & 0 deletions src/parser/classes/VideoDescriptionMusicSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type ObservedArray, YTNode } from '../helpers.js';
import CarouselLockup from './CarouselLockup.js';
import Parser, { type RawNode } from '../index.js';
import { Text } from '../misc.js';

export default class VideoDescriptionMusicSection extends YTNode {
static type = 'VideoDescriptionMusicSection';

carousel_lockups: ObservedArray<CarouselLockup>;
section_title: Text;
constructor(data: RawNode) {
super();
this.carousel_lockups = Parser.parseArray(data.carouselLockups, CarouselLockup);
this.section_title = new Text(data.sectionTitle);
}
}
8 changes: 8 additions & 0 deletions src/parser/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export { default as Card } from './classes/Card.js';
export { default as CardCollection } from './classes/CardCollection.js';
export { default as CarouselHeader } from './classes/CarouselHeader.js';
export { default as CarouselItem } from './classes/CarouselItem.js';
export { default as CarouselLockup } from './classes/CarouselLockup.js';
export { default as Channel } from './classes/Channel.js';
export { default as ChannelAboutFullMetadata } from './classes/ChannelAboutFullMetadata.js';
export { default as ChannelAgeGate } from './classes/ChannelAgeGate.js';
Expand Down Expand Up @@ -88,9 +89,12 @@ export { default as Endscreen } from './classes/Endscreen.js';
export { default as EndscreenElement } from './classes/EndscreenElement.js';
export { default as EndScreenPlaylist } from './classes/EndScreenPlaylist.js';
export { default as EndScreenVideo } from './classes/EndScreenVideo.js';
export { default as EngagementPanelSectionList } from './classes/EngagementPanelSectionList.js';
export { default as ExpandableMetadata } from './classes/ExpandableMetadata.js';
export { default as ExpandableTab } from './classes/ExpandableTab.js';
export { default as ExpandableVideoDescriptionBody } from './classes/ExpandableVideoDescriptionBody.js';
export { default as ExpandedShelfContents } from './classes/ExpandedShelfContents.js';
export { default as Factoid } from './classes/Factoid.js';
export { default as FeedFilterChipBar } from './classes/FeedFilterChipBar.js';
export { default as FeedTabbedHeader } from './classes/FeedTabbedHeader.js';
export { default as GameCard } from './classes/GameCard.js';
Expand Down Expand Up @@ -121,6 +125,7 @@ export { default as HorizontalMovieList } from './classes/HorizontalMovieList.js
export { default as IconLink } from './classes/IconLink.js';
export { default as InfoPanelContainer } from './classes/InfoPanelContainer.js';
export { default as InfoPanelContent } from './classes/InfoPanelContent.js';
export { default as InfoRow } from './classes/InfoRow.js';
export { default as InteractiveTabbedHeader } from './classes/InteractiveTabbedHeader.js';
export { default as ItemSection } from './classes/ItemSection.js';
export { default as ItemSectionHeader } from './classes/ItemSectionHeader.js';
Expand Down Expand Up @@ -299,6 +304,7 @@ export { default as SingleHeroImage } from './classes/SingleHeroImage.js';
export { default as SlimOwner } from './classes/SlimOwner.js';
export { default as SlimVideoMetadata } from './classes/SlimVideoMetadata.js';
export { default as SortFilterSubMenu } from './classes/SortFilterSubMenu.js';
export { default as StructuredDescriptionContent } from './classes/StructuredDescriptionContent.js';
export { default as SubFeedOption } from './classes/SubFeedOption.js';
export { default as SubFeedSelector } from './classes/SubFeedSelector.js';
export { default as SubscribeButton } from './classes/SubscribeButton.js';
Expand Down Expand Up @@ -335,6 +341,8 @@ export { default as VerticalList } from './classes/VerticalList.js';
export { default as VerticalWatchCardList } from './classes/VerticalWatchCardList.js';
export { default as Video } from './classes/Video.js';
export { default as VideoCard } from './classes/VideoCard.js';
export { default as VideoDescriptionHeader } from './classes/VideoDescriptionHeader.js';
export { default as VideoDescriptionMusicSection } from './classes/VideoDescriptionMusicSection.js';
export { default as VideoInfoCardContent } from './classes/VideoInfoCardContent.js';
export { default as VideoOwner } from './classes/VideoOwner.js';
export { default as VideoPrimaryInfo } from './classes/VideoPrimaryInfo.js';
Expand Down
12 changes: 10 additions & 2 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ export default class Parser {
parsed_data.cards = cards;
}

const engagement_panels = data.engagementPanels?.map((e) => {
const item = this.parseItem(e, YTNodes.EngagementPanelSectionList) as YTNodes.EngagementPanelSectionList;
return item;
});
if (engagement_panels) {
parsed_data.engagement_panels = engagement_panels;
}
this.#createMemo();
const items = this.parse(data.items);
if (items) {
Expand Down Expand Up @@ -502,7 +509,8 @@ export default class Parser {
'BrandVideoShelf',
'BrandVideoSingleton',
'StatementBanner',
'GuideSigninPromo'
'GuideSigninPromo',
'AdsEngagementPanelContent'
]);

static shouldIgnore(classname: string) {
Expand Down Expand Up @@ -739,4 +747,4 @@ export class LiveChatContinuation extends YTNode {

this.viewer_name = data.viewerName;
}
}
}
6 changes: 4 additions & 2 deletions src/parser/types/ParsedResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type VideoDetails from '../classes/misc/VideoDetails.js';
import type Alert from '../classes/Alert.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
import type PlayerAnnotationsExpanded from '../classes/PlayerAnnotationsExpanded.js';

import type EngagementPanelSectionList from '../classes/EngagementPanelSectionList.js';
export interface IParsedResponse {
actions?: SuperParsedResult<YTNode>;
actions_memo?: Memo;
Expand Down Expand Up @@ -73,6 +73,7 @@ export interface IParsedResponse {
storyboards?: PlayerStoryboardSpec | PlayerLiveStoryboardSpec;
endscreen?: Endscreen;
cards?: CardCollection;
engagement_panels?: EngagementPanelSectionList[];
items?: SuperParsedResult<YTNode>;
}

Expand Down Expand Up @@ -111,6 +112,7 @@ export interface INextResponse {
on_response_received_endpoints?: ObservedArray<ReloadContinuationItemsCommand | AppendContinuationItemsAction>;
on_response_received_endpoints_memo?: Memo;
player_overlays?: SuperParsedResult<YTNode>;
engagement_panels?: EngagementPanelSectionList[];
}

export interface IBrowseResponse {
Expand Down Expand Up @@ -163,4 +165,4 @@ export interface IUpdatedMetadataResponse {
export interface IGuideResponse {
items: SuperParsedResult<YTNode>;
items_memo: Memo;
}
}
3 changes: 2 additions & 1 deletion src/parser/types/RawResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ export interface IRawResponse {
cards?: RawNode;
items?: RawNode[];
frameworkUpdates?: any;
}
engagementPanels: RawNode[];
}
69 changes: 38 additions & 31 deletions src/parser/youtube/VideoInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import type { ObservedArray, YTNode } from '../helpers.js';

import { InnertubeError } from '../../utils/Utils.js';
import { MediaInfo } from '../../core/mixins/index.js';
import StructuredDescriptionContent from '../classes/StructuredDescriptionContent.js';
import { VideoDescriptionMusicSection } from '../nodes.js';

class VideoInfo extends MediaInfo {
#watch_next_continuation?: ContinuationItem;
Expand Down Expand Up @@ -336,41 +338,46 @@ class VideoInfo extends MediaInfo {
/**
* Get songs used in the video.
*/
// TODO: this seems to be broken with the new UI, further investigation needed
get music_tracks() {
/*
Const metadata = this.secondary_info?.metadata;
if (!metadata)
return [];
const songs = [];
let current_song: Record<string, Text[]> = {};
let is_music_section = false;
for (let i = 0; i < metadata.rows.length; i++) {
const row = metadata.rows[i];
if (row.is(MetadataRowHeader)) {
if (row.content?.toString().toLowerCase().startsWith('music')) {
is_music_section = true;
i++; // Skip the learn more link
const description_content = this.page[1]?.engagement_panels?.filter((panel) => panel.content?.is(StructuredDescriptionContent));
if (description_content !== undefined && description_content.length > 0) {
const music_section = (description_content[0].content as StructuredDescriptionContent)?.items.filter((item: YTNode) => item?.is(VideoDescriptionMusicSection)) as VideoDescriptionMusicSection[];
if (music_section !== undefined && music_section.length > 0) {
return music_section[0].carousel_lockups?.map((lookup) => {
let song, artist, album, license, videoId, channelId;
// If the song isn't in the video_lockup, it should be in the info_rows
song = lookup.video_lockup?.title;
// If the video id isn't in the video_lockup, it should be in the info_rows
videoId = lookup.video_lockup?.endpoint.payload.videoId;
for (let i = 0; i < lookup.info_rows.length; i++) {
const info_row = lookup.info_rows[i];
if (info_row.info_row_expand_status_key === undefined) {
if (song === undefined) {
song = info_row.metadata_text;
if (videoId === undefined) {
videoId = info_row.metadata_endpoint?.payload;
}
continue;
}
if (!is_music_section)
continue;
if (row.is(MetadataRow))
current_song[row.title?.toString().toLowerCase().replace(/ /g, '_')] = row.contents;
// TODO: this makes no sense, we continue above when
if (row.has_divider_line) {
songs.push(current_song);
current_song = {};
} else {
album = info_row.metadata_text;
}
} else {
if (info_row.info_row_expand_status_key?.indexOf('structured-description-music-section-artists-row-state-id') !== -1) {
artist = info_row.metadata_text;
if (channelId === undefined) {
channelId = info_row.metadata_endpoint?.payload?.browseId;
}
}
if (info_row.info_row_expand_status_key?.indexOf('structured-description-music-section-licenses-row-state-id') !== -1) {
license = info_row.metadata_text;
}
}
}
if (is_music_section)
songs.push(current_song);
return songs;
*/
}
return { song, artist, album, license, videoId, channelId };
});
}
}
return [];
}
}

export default VideoInfo;
export default VideoInfo;

0 comments on commit e434bb2

Please sign in to comment.