feat: add support for chapters & video heatmap (#263)

* feat: add support for chapters & video heatmap

* chore: add tests
This commit is contained in:
LuanRT
2022-12-27 04:17:05 -03:00
committed by GitHub
parent 2b3642ba63
commit 6a4b4f3359
9 changed files with 183 additions and 4 deletions

View File

@@ -0,0 +1,21 @@
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
import { YTNode } from '../helpers';
class Chapter extends YTNode {
static type = 'Chapter';
title: Text;
time_range_start_millis: number;
thumbnail: Thumbnail[];
constructor(data: any) {
super();
this.title = new Text(data.title);
this.time_range_start_millis = data.timeRangeStartMillis;
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
}
}
export default Chapter;

View File

@@ -0,0 +1,19 @@
import Parser from '..';
import { YTNode } from '../helpers';
import type Button from './Button';
import type MultiMarkersPlayerBar from './MultiMarkersPlayerBar';
class DecoratedPlayerBar extends YTNode {
static type = 'DecoratedPlayerBar';
player_bar: MultiMarkersPlayerBar | null;
player_bar_action_button: Button | null;
constructor(data: any) {
super();
this.player_bar = Parser.parseItem<MultiMarkersPlayerBar>(data.playerBar);
this.player_bar_action_button = Parser.parseItem<Button>(data.playerBarActionButton);
}
}
export default DecoratedPlayerBar;

View File

@@ -0,0 +1,18 @@
import { YTNode } from '../helpers';
class HeatMarker extends YTNode {
static type = 'HeatMarker';
time_range_start_millis: number;
marker_duration_millis: number;
heat_marker_intensity_score_normalized: number;
constructor(data: any) {
super();
this.time_range_start_millis = data.timeRangeStartMillis;
this.marker_duration_millis = data.markerDurationMillis;
this.heat_marker_intensity_score_normalized = data.heatMarkerIntensityScoreNormalized;
}
}
export default HeatMarker;

View File

@@ -0,0 +1,25 @@
import Parser from '..';
import type HeatMarker from './HeatMarker';
import { YTNode } from '../helpers';
class Heatmap extends YTNode {
static type = 'Heatmap';
max_height_dp: number;
min_height_dp: number;
show_hide_animation_duration_millis: number;
heat_markers: HeatMarker[];
heat_markers_decorations: any;
constructor(data: any) {
super();
this.max_height_dp = data.maxHeightDp;
this.min_height_dp = data.minHeightDp;
this.show_hide_animation_duration_millis = data.showHideAnimationDurationMillis;
this.heat_markers = Parser.parseArray<HeatMarker>(data.heatMarkers);
this.heat_markers_decorations = Parser.parseArray(data.heatMarkersDecorations);
}
}
export default Heatmap;

View File

@@ -0,0 +1,44 @@
import Parser from '..';
import type Chapter from './Chapter';
import type Heatmap from './Heatmap';
import { observe, ObservedArray, YTNode } from '../helpers';
class Marker extends YTNode {
static type = 'Marker';
marker_key: string;
value: {
heatmap?: Heatmap | null;
chapters?: Chapter[];
};
constructor (data: any) {
super();
this.marker_key = data.key;
this.value = {};
if (data.value.heatmap) {
this.value.heatmap = Parser.parseItem<Heatmap>(data.value.heatmap);
}
if (data.value.chapters) {
this.value.chapters = Parser.parseArray<Chapter>(data.value.chapters);
}
}
}
class MultiMarkersPlayerBar extends YTNode {
static type = 'MultiMarkersPlayerBar';
markers_map: ObservedArray<Marker>;
constructor(data: any) {
super();
this.markers_map = observe(data.markersMap?.map((marker: { key: string; value: { [key: string ]: any }}) => new Marker(marker)));
}
}
export { Marker };
export default MultiMarkersPlayerBar;

View File

@@ -3,6 +3,7 @@ import Menu from './menus/Menu';
import Button from './Button';
import WatchNextEndScreen from './WatchNextEndScreen';
import PlayerOverlayAutoplay from './PlayerOverlayAutoplay';
import type DecoratedPlayerBar from './DecoratedPlayerBar';
import { YTNode } from '../helpers';
@@ -16,6 +17,7 @@ class PlayerOverlay extends YTNode {
fullscreen_engagement;
actions;
browser_media_session;
decorated_player_bar;
constructor(data: any) {
super();
@@ -26,6 +28,7 @@ class PlayerOverlay extends YTNode {
this.fullscreen_engagement = Parser.parse(data.fullscreenEngagement);
this.actions = Parser.parseArray(data.actions);
this.browser_media_session = Parser.parseItem(data.browserMediaSession);
this.decorated_player_bar = Parser.parseItem<DecoratedPlayerBar>(data.decoratedPlayerBarRenderer);
}
}

View File

@@ -0,0 +1,23 @@
import Text from './misc/Text';
import { YTNode } from '../helpers';
class TimedMarkerDecoration extends YTNode {
static type = 'TimedMarkerDecoration';
visible_time_range_start_millis: number;
visible_time_range_end_millis: number;
decoration_time_millis: number;
label: Text;
icon: string;
constructor(data: any) {
super();
this.visible_time_range_start_millis = data.visibleTimeRangeStartMillis;
this.visible_time_range_end_millis = data.visibleTimeRangeEndMillis;
this.decoration_time_millis = data.decorationTimeMillis;
this.label = new Text(data.label);
this.icon = data.icon;
}
}
export default TimedMarkerDecoration;

View File

@@ -39,6 +39,7 @@ import { default as ChannelMobileHeader } from './classes/ChannelMobileHeader';
import { default as ChannelOptions } from './classes/ChannelOptions';
import { default as ChannelThumbnailWithLink } from './classes/ChannelThumbnailWithLink';
import { default as ChannelVideoPlayer } from './classes/ChannelVideoPlayer';
import { default as Chapter } from './classes/Chapter';
import { default as ChildVideo } from './classes/ChildVideo';
import { default as ChipCloud } from './classes/ChipCloud';
import { default as ChipCloudChip } from './classes/ChipCloudChip';
@@ -62,6 +63,7 @@ import { default as ConfirmDialog } from './classes/ConfirmDialog';
import { default as ContinuationItem } from './classes/ContinuationItem';
import { default as CopyLink } from './classes/CopyLink';
import { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog';
import { default as DecoratedPlayerBar } from './classes/DecoratedPlayerBar';
import { default as DefaultPromoPanel } from './classes/DefaultPromoPanel';
import { default as DidYouMean } from './classes/DidYouMean';
import { default as DownloadButton } from './classes/DownloadButton';
@@ -85,6 +87,8 @@ import { default as GridChannel } from './classes/GridChannel';
import { default as GridHeader } from './classes/GridHeader';
import { default as GridPlaylist } from './classes/GridPlaylist';
import { default as GridVideo } from './classes/GridVideo';
import { default as Heatmap } from './classes/Heatmap';
import { default as HeatMarker } from './classes/HeatMarker';
import { default as HighlightsCarousel } from './classes/HighlightsCarousel';
import { default as HistorySuggestion } from './classes/HistorySuggestion';
import { default as HorizontalCardList } from './classes/HorizontalCardList';
@@ -161,6 +165,7 @@ import { default as MicroformatData } from './classes/MicroformatData';
import { default as Mix } from './classes/Mix';
import { default as Movie } from './classes/Movie';
import { default as MovingThumbnail } from './classes/MovingThumbnail';
import { default as MultiMarkersPlayerBar } from './classes/MultiMarkersPlayerBar';
import { default as MusicCarouselShelf } from './classes/MusicCarouselShelf';
import { default as MusicCarouselShelfBasicHeader } from './classes/MusicCarouselShelfBasicHeader';
import { default as MusicDescriptionShelf } from './classes/MusicDescriptionShelf';
@@ -269,6 +274,7 @@ import { default as ThumbnailOverlayResumePlayback } from './classes/ThumbnailOv
import { default as ThumbnailOverlaySidePanel } from './classes/ThumbnailOverlaySidePanel';
import { default as ThumbnailOverlayTimeStatus } from './classes/ThumbnailOverlayTimeStatus';
import { default as ThumbnailOverlayToggleButton } from './classes/ThumbnailOverlayToggleButton';
import { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoration';
import { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader';
import { default as ToggleButton } from './classes/ToggleButton';
import { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem';
@@ -331,6 +337,7 @@ export const YTNodes = {
ChannelOptions,
ChannelThumbnailWithLink,
ChannelVideoPlayer,
Chapter,
ChildVideo,
ChipCloud,
ChipCloudChip,
@@ -354,6 +361,7 @@ export const YTNodes = {
ContinuationItem,
CopyLink,
CreatePlaylistDialog,
DecoratedPlayerBar,
DefaultPromoPanel,
DidYouMean,
DownloadButton,
@@ -377,6 +385,8 @@ export const YTNodes = {
GridHeader,
GridPlaylist,
GridVideo,
Heatmap,
HeatMarker,
HighlightsCarousel,
HistorySuggestion,
HorizontalCardList,
@@ -453,6 +463,7 @@ export const YTNodes = {
Mix,
Movie,
MovingThumbnail,
MultiMarkersPlayerBar,
MusicCarouselShelf,
MusicCarouselShelfBasicHeader,
MusicDescriptionShelf,
@@ -561,6 +572,7 @@ export const YTNodes = {
ThumbnailOverlaySidePanel,
ThumbnailOverlayTimeStatus,
ThumbnailOverlayToggleButton,
TimedMarkerDecoration,
TitleAndButtonListHeader,
ToggleButton,
ToggleMenuServiceItem,

View File

@@ -19,10 +19,27 @@ describe('YouTube.js Tests', () => {
expect(info.basic_info.id).toBe(VIDEOS[0].ID);
});
it('should have captions on full video info', async () => {
it('should have captions', () => {
expect(info.captions?.caption_tracks.length).toBeGreaterThan(0);
});
it('should have chapters', () => {
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
const chapters = (
markers_map?.get({ marker_key: 'AUTO_CHAPTERS' }) ||
markers_map?.get({ marker_key: 'DESCRIPTION_CHAPTERS' })
)?.value?.chapters;
expect(chapters).toBeDefined();
});
it('should have heatmap', () => {
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
const heatmap = markers_map?.get({ marker_key: 'HEATSEEKER' })?.value?.heatmap;
expect(heatmap).toBeDefined();
});
it('should retrieve basic video info', async () => {
const b_info = await yt.getBasicInfo(VIDEOS[0].ID);
expect(b_info.basic_info.id).toBe(VIDEOS[0].ID);
@@ -114,9 +131,6 @@ describe('YouTube.js Tests', () => {
const filtered_list = await videos_tab.applyFilter('Popular');
expect(filtered_list.videos.length).toBeGreaterThan(0);
const search = await channel.search('e-ink');
expect(search.videos.length).toBeGreaterThan(0);
});
it('should retrieve home feed', async () => {