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: Add support for retrieving transcripts #500

Merged
merged 7 commits into from
Sep 10, 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,16 @@ console.info('Playback url:', url);
| video_id | `string` | Video id |
| options | `FormatOptions` | Format options |

<a name="gettranscript"></a>
### `getTranscript(video_id)`
Retrieves a given video's transcript.

**Returns**: `Promise<Transcript>`

| Param | Type | Description |
| --- | --- | --- |
| video_id | `string` | Video id |

<a name="download"></a>
### `download(video_id, options?)`
Downloads a given video.
Expand Down
33 changes: 32 additions & 1 deletion src/Innertube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import NotificationsMenu from './parser/youtube/NotificationsMenu.js';
import Playlist from './parser/youtube/Playlist.js';
import Search from './parser/youtube/Search.js';
import VideoInfo from './parser/youtube/VideoInfo.js';
import ContinuationItem from './parser/classes/ContinuationItem.js';
import Transcript from './parser/classes/Transcript.js';

import { Kids, Music, Studio } from './core/clients/index.js';
import { AccountManager, InteractionManager, PlaylistManager } from './core/managers/index.js';
Expand All @@ -36,7 +38,7 @@ import {
import { GetUnseenCountEndpoint } from './core/endpoints/notification/index.js';

import type { ApiResponse } from './core/Actions.js';
import type { IBrowseResponse, IParsedResponse } from './parser/types/index.js';
import { type IGetTranscriptResponse, type IBrowseResponse, type IParsedResponse } from './parser/types/index.js';
import type { INextRequest } from './types/index.js';
import type { DownloadOptions, FormatOptions } from './types/FormatUtils.js';

Expand Down Expand Up @@ -332,6 +334,35 @@ export default class Innertube {
return info.chooseFormat(options);
}

/**
* Retrieves a video's transcript.
* @param video_id - The video id.
*/
async getTranscript(video_id: string): Promise<Transcript> {
throwIfMissing({ video_id });

const next_response = await this.actions.execute(NextEndpoint.PATH, { ...NextEndpoint.build({ video_id }), parse: true });

if (!next_response.engagement_panels)
throw new InnertubeError('Engagement panels not found. Video likely has no transcript.');

const transcript_panel = next_response.engagement_panels.get({
panel_identifier: 'engagement-panel-searchable-transcript'
});

if (!transcript_panel)
throw new InnertubeError('Transcript panel not found. Video likely has no transcript.');

const transcript_continuation = transcript_panel.content?.as(ContinuationItem);

if (!transcript_continuation)
throw new InnertubeError('Transcript continuation not found.');

const transcript_response = await transcript_continuation.endpoint.call<IGetTranscriptResponse>(this.actions, { parse: true });

return transcript_response.actions_memo.getType(Transcript).first();
}

/**
* Downloads a given video. If you only need the direct download link see {@link getStreamingData}.
* If you wish to retrieve the video info too, have a look at {@link getBasicInfo} or {@link getInfo}.
Expand Down
7 changes: 4 additions & 3 deletions src/parser/classes/EngagementPanelSectionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ import Parser, { type RawNode } from '../index.js';
import ContinuationItem from './ContinuationItem.js';
import EngagementPanelTitleHeader from './EngagementPanelTitleHeader.js';
import MacroMarkersList from './MacroMarkersList.js';
import ProductList from './ProductList.js';
import SectionList from './SectionList.js';
import StructuredDescriptionContent from './StructuredDescriptionContent.js';

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

header: EngagementPanelTitleHeader | null;
content: SectionList | ContinuationItem | StructuredDescriptionContent | MacroMarkersList | null;
content: SectionList | ContinuationItem | StructuredDescriptionContent | MacroMarkersList | ProductList | null;
target_id?: string;
panel_identifier?: string;
visibility?: string;

constructor(data: RawNode) {
super();
this.header = Parser.parseItem(data.header, EngagementPanelTitleHeader);
this.content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent, MacroMarkersList ]);
this.content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent, MacroMarkersList, ProductList ]);
this.panel_identifier = data.panelIdentifier;
this.target_id = data.targetId;
this.visibility = data.visibility;
}
}
}
5 changes: 3 additions & 2 deletions src/parser/classes/ExpandableMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { YTNode } from '../helpers.js';
import Parser, { type RawNode } from '../index.js';
import Button from './Button.js';
import HorizontalCardList from './HorizontalCardList.js';
import HorizontalList from './HorizontalList.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';

Expand All @@ -15,7 +16,7 @@ export default class ExpandableMetadata extends YTNode {
expanded_title: Text;
};

expanded_content: HorizontalCardList | null;
expanded_content: HorizontalCardList | HorizontalList | null;
expand_button: Button | null;
collapse_button: Button | null;

Expand All @@ -31,7 +32,7 @@ export default class ExpandableMetadata extends YTNode {
};
}

this.expanded_content = Parser.parseItem(data.expandedContent, HorizontalCardList);
this.expanded_content = Parser.parseItem(data.expandedContent, [ HorizontalCardList, HorizontalList ]);
this.expand_button = Parser.parseItem(data.expandButton, Button);
this.collapse_button = Parser.parseItem(data.collapseButton, Button);
}
Expand Down
16 changes: 16 additions & 0 deletions src/parser/classes/FancyDismissibleDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import { Text } from '../misc.js';

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

dialog_message: Text;
confirm_label: Text;

constructor(data: RawNode) {
super();
this.dialog_message = new Text(data.dialogMessage);
this.confirm_label = new Text(data.confirmLabel);
}
}
15 changes: 15 additions & 0 deletions src/parser/classes/ProductList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ObservedArray} from '../helpers.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';

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

contents: ObservedArray<YTNode>;

constructor(data: RawNode) {
super();
this.contents = Parser.parseArray(data.contents);
}
}
16 changes: 16 additions & 0 deletions src/parser/classes/ProductListHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import { Text } from '../misc.js';

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

title: Text;
suppress_padding_disclaimer: boolean;

constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.suppress_padding_disclaimer = !!data.suppressPaddingDisclaimer;
}
}
31 changes: 31 additions & 0 deletions src/parser/classes/ProductListItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import { Text, Thumbnail } from '../misc.js';
import Button from './Button.js';
import NavigationEndpoint from './NavigationEndpoint.js';

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

title: Text;
accessibility_title: string;
thumbnail: Thumbnail[];
price: string;
endpoint: NavigationEndpoint;
merchant_name: string;
stay_in_app: boolean;
view_button: Button | null;

constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.accessibility_title = data.accessibilityTitle;
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
this.price = data.price;
this.endpoint = new NavigationEndpoint(data.onClickCommand);
this.merchant_name = data.merchantName;
this.stay_in_app = !!data.stayInApp;
this.view_button = Parser.parseItem(data.viewButton, Button);
}
}
6 changes: 5 additions & 1 deletion src/parser/classes/StructuredDescriptionContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import HorizontalCardList from './HorizontalCardList.js';
import VideoDescriptionHeader from './VideoDescriptionHeader.js';
import VideoDescriptionInfocardsSection from './VideoDescriptionInfocardsSection.js';
import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.js';
import type VideoDescriptionTranscriptSection from './VideoDescriptionTranscriptSection.js';

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

items: ObservedArray<VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection | VideoDescriptionInfocardsSection | HorizontalCardList>;
items: ObservedArray<
VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection |
VideoDescriptionInfocardsSection | VideoDescriptionTranscriptSection | HorizontalCardList
>;

constructor(data: RawNode) {
super();
Expand Down
15 changes: 15 additions & 0 deletions src/parser/classes/Transcript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import TranscriptSearchPanel from './TranscriptSearchPanel.js';

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

content: TranscriptSearchPanel | null;

constructor(data: RawNode) {
super();
this.content = Parser.parseItem(data.content, TranscriptSearchPanel);
}
}
15 changes: 15 additions & 0 deletions src/parser/classes/TranscriptFooter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import SortFilterSubMenu from './SortFilterSubMenu.js';

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

language_menu: SortFilterSubMenu | null;

constructor(data: RawNode) {
super();
this.language_menu = Parser.parseItem(data.languageMenu, SortFilterSubMenu);
}
}
23 changes: 23 additions & 0 deletions src/parser/classes/TranscriptSearchBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import Button from './Button.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import { Text } from '../misc.js';

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

formatted_placeholder: Text;
clear_button: Button | null;
endpoint: NavigationEndpoint;
search_button: Button | null;

constructor(data: RawNode) {
super();
this.formatted_placeholder = new Text(data.formattedPlaceholder);
this.clear_button = Parser.parseItem(data.clearButton, Button);
this.endpoint = new NavigationEndpoint(data.onTextChangeCommand);
this.search_button = Parser.parseItem(data.searchButton, Button);
}
}
23 changes: 23 additions & 0 deletions src/parser/classes/TranscriptSearchPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import TranscriptFooter from './TranscriptFooter.js';
import TranscriptSearchBox from './TranscriptSearchBox.js';
import TranscriptSegmentList from './TranscriptSegmentList.js';

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

header: TranscriptSearchBox | null;
body: TranscriptSegmentList | null;
footer: TranscriptFooter | null;
target_id: string;

constructor(data: RawNode) {
super();
this.header = Parser.parseItem(data.header, TranscriptSearchBox);
this.body = Parser.parseItem(data.body, TranscriptSegmentList);
this.footer = Parser.parseItem(data.footer, TranscriptFooter);
this.target_id = data.targetId;
}
}
22 changes: 22 additions & 0 deletions src/parser/classes/TranscriptSegment.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 TranscriptSegment extends YTNode {
static type = 'TranscriptSegment';

start_ms: string;
end_ms: string;
snippet: Text;
start_time_text: Text;
target_id: string;

constructor(data: RawNode) {
super();
this.start_ms = data.startMs;
this.end_ms = data.endMs;
this.snippet = new Text(data.snippet);
this.start_time_text = new Text(data.startTimeText);
this.target_id = data.targetId;
}
}
23 changes: 23 additions & 0 deletions src/parser/classes/TranscriptSegmentList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ObservedArray} from '../helpers.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import { Text } from '../misc.js';
import TranscriptSegment from './TranscriptSegment.js';

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

initial_segments: ObservedArray<TranscriptSegment>;
no_result_label: Text;
retry_label: Text;
touch_captions_enabled: boolean;

constructor(data: RawNode) {
super();
this.initial_segments = Parser.parseArray(data.initialSegments, TranscriptSegment);
this.no_result_label = new Text(data.noResultLabel);
this.retry_label = new Text(data.retryLabel);
this.touch_captions_enabled = data.touchCaptionsEnabled;
}
}
15 changes: 15 additions & 0 deletions src/parser/classes/UploadTimeFactoid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import Factoid from './Factoid.js';

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

factoid: Factoid | null;

constructor(data: RawNode) {
super();
this.factoid = Parser.parseItem(data.factoid, Factoid);
}
}
Loading
Loading