Skip to content
Open
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
29 changes: 19 additions & 10 deletions youtube_transcript_api/_transcripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from itertools import chain

from html import unescape
from typing import List, Dict, Iterator, Iterable, Pattern, Optional
from typing import List, Dict, Iterator, Iterable, Pattern, Optional, Tuple

from defusedxml import ElementTree

Expand Down Expand Up @@ -55,6 +55,7 @@ class FetchedTranscript:

snippets: List[FetchedTranscriptSnippet]
video_id: str
title: str
language: str
language_code: str
is_generated: bool
Expand Down Expand Up @@ -105,6 +106,7 @@ def __init__(
self,
http_client: Session,
video_id: str,
title: str,
url: str,
language: str,
language_code: str,
Expand All @@ -117,6 +119,7 @@ def __init__(
"""
self._http_client = http_client
self.video_id = video_id
self.title = title
self._url = url
self.language = language
self.language_code = language_code
Expand All @@ -141,6 +144,7 @@ def fetch(self, preserve_formatting: bool = False) -> FetchedTranscript:
return FetchedTranscript(
snippets=snippets,
video_id=self.video_id,
title=self.title,
language=self.language,
language_code=self.language_code,
is_generated=self.is_generated,
Expand All @@ -167,6 +171,7 @@ def translate(self, language_code: str) -> "Transcript":
return Transcript(
self._http_client,
self.video_id,
self.title,
"{url}&tlang={language_code}".format(
url=self._url, language_code=language_code
),
Expand Down Expand Up @@ -205,14 +210,15 @@ def __init__(

@staticmethod
def build(
http_client: Session, video_id: str, captions_json: Dict
http_client: Session, video_id: str, captions_json: Dict, title: str
) -> "TranscriptList":
"""
Factory method for TranscriptList.

:param http_client: http client which is used to make the transcript retrieving http calls
:param video_id: the id of the video this TranscriptList is for
:param captions_json: the JSON parsed from the YouTube pages static HTML
:param title: the title of the video
:return: the created TranscriptList
"""
translation_languages = [
Expand All @@ -235,6 +241,7 @@ def build(
transcript_dict[caption["languageCode"]] = Transcript(
http_client,
video_id,
title,
caption["baseUrl"].replace("&fmt=srv3", ""),
caption["name"]["runs"][0]["text"],
caption["languageCode"],
Expand Down Expand Up @@ -350,26 +357,25 @@ def __init__(self, http_client: Session, proxy_config: Optional[ProxyConfig]):
self._proxy_config = proxy_config

def fetch(self, video_id: str) -> TranscriptList:
return TranscriptList.build(
self._http_client,
video_id,
self._fetch_captions_json(video_id),
)
captions_json, title = self._fetch_video_data(video_id)
return TranscriptList.build(self._http_client, video_id, captions_json, title)

def _fetch_captions_json(self, video_id: str, try_number: int = 0) -> Dict:
def _fetch_video_data(self, video_id: str, try_number: int = 0) -> Tuple[Dict, str]:
try:
html = self._fetch_video_html(video_id)
api_key = self._extract_innertube_api_key(html, video_id)
innertube_data = self._fetch_innertube_data(video_id, api_key)
return self._extract_captions_json(innertube_data, video_id)
captions_json = self._extract_captions_json(innertube_data, video_id)
title = self._extract_video_title(innertube_data)
return captions_json, title
except RequestBlocked as exception:
retries = (
0
if self._proxy_config is None
else self._proxy_config.retries_when_blocked
)
if try_number + 1 < retries:
return self._fetch_captions_json(video_id, try_number=try_number + 1)
return self._fetch_video_data(video_id, try_number=try_number + 1)
raise exception.with_proxy_config(self._proxy_config)

def _extract_innertube_api_key(self, html: str, video_id: str) -> str:
Expand All @@ -392,6 +398,9 @@ def _extract_captions_json(self, innertube_data: Dict, video_id: str) -> Dict:

return captions_json

def _extract_video_title(self, innertube_data: Dict) -> str:
return innertube_data.get("videoDetails", {}).get("title")

def _assert_playability(self, playability_status_data: Dict, video_id: str) -> None:
playability_status = playability_status_data.get("status")
if (
Expand Down
3 changes: 2 additions & 1 deletion youtube_transcript_api/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def setUp(self):
language_code="en",
is_generated=False,
video_id="GJLlxj_dtq8",
title="Surface Go Review - It’s Awesome",
)
self.ref_transcript_raw = self.ref_transcript.to_raw_data()

Expand All @@ -91,7 +92,6 @@ def setUp(self):

def test_fetch(self):
transcript = YouTubeTranscriptApi().fetch("GJLlxj_dtq8")

self.assertEqual(
transcript,
self.ref_transcript,
Expand All @@ -114,6 +114,7 @@ def test_fetch__with_altered_user_agent(self):
responses.POST,
"https://www.youtube.com/youtubei/v1/player",
body=load_asset("youtube_altered_user_agent.innertube.json.static"),
content_type="application/json",
)

transcript = YouTubeTranscriptApi().fetch("GJLlxj_dtq8")
Expand Down
1 change: 1 addition & 0 deletions youtube_transcript_api/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def setUp(self):
language_code="en",
is_generated=True,
video_id="GJLlxj_dtq8",
title="Video Title",
)
)
self.transcript_mock.translate = MagicMock(return_value=self.transcript_mock)
Expand Down
1 change: 1 addition & 0 deletions youtube_transcript_api/test/test_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def setUp(self):
language_code="en",
is_generated=True,
video_id="12345",
title="Video Title",
)
self.transcripts = [self.transcript, self.transcript]
self.transcript_raw = self.transcript.to_raw_data()
Expand Down
Loading