From f23ba6ab2156604ae338e47cb82f4f11c88a6075 Mon Sep 17 00:00:00 2001 From: Ahmet Kaya Date: Fri, 8 May 2026 20:36:16 +0300 Subject: [PATCH 1/5] feat: add title metadata to transcript fetching --- youtube_transcript_api/_transcripts.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/youtube_transcript_api/_transcripts.py b/youtube_transcript_api/_transcripts.py index 55baa426..14b5b07e 100644 --- a/youtube_transcript_api/_transcripts.py +++ b/youtube_transcript_api/_transcripts.py @@ -55,6 +55,7 @@ class FetchedTranscript: snippets: List[FetchedTranscriptSnippet] video_id: str + title: str language: str language_code: str is_generated: bool @@ -105,6 +106,7 @@ def __init__( self, http_client: Session, video_id: str, + title: str, url: str, language: str, language_code: str, @@ -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 @@ -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, @@ -167,13 +171,14 @@ 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 ), self._translation_languages_dict[language_code], language_code, True, - [], + [] ) @@ -205,7 +210,7 @@ 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. @@ -213,6 +218,7 @@ def build( :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 = [ @@ -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"], @@ -350,18 +357,22 @@ def __init__(self, http_client: Session, proxy_config: Optional[ProxyConfig]): self._proxy_config = proxy_config def fetch(self, video_id: str) -> TranscriptList: + captions_json, title = self._fetch_video_data(video_id) return TranscriptList.build( self._http_client, video_id, - self._fetch_captions_json(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 @@ -369,7 +380,7 @@ def _fetch_captions_json(self, video_id: str, try_number: int = 0) -> Dict: 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: @@ -392,6 +403,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 ( From a618aec18b3db8a7ac61be672aa1bf12ff28fe99 Mon Sep 17 00:00:00 2001 From: Ahmet Kaya Date: Fri, 8 May 2026 20:36:26 +0300 Subject: [PATCH 2/5] feat: add title metadata and content type to fetch tests --- youtube_transcript_api/test/test_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/youtube_transcript_api/test/test_api.py b/youtube_transcript_api/test/test_api.py index 60060347..0c364244 100644 --- a/youtube_transcript_api/test/test_api.py +++ b/youtube_transcript_api/test/test_api.py @@ -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() @@ -91,7 +92,6 @@ def setUp(self): def test_fetch(self): transcript = YouTubeTranscriptApi().fetch("GJLlxj_dtq8") - self.assertEqual( transcript, self.ref_transcript, @@ -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") From 65ff88df8276b39e67eaf676c4253bd40f948a91 Mon Sep 17 00:00:00 2001 From: Ahmet Kaya Date: Fri, 8 May 2026 20:36:41 +0300 Subject: [PATCH 3/5] feat: add title metadata to transcript test cases --- youtube_transcript_api/test/test_cli.py | 1 + youtube_transcript_api/test/test_formatters.py | 1 + 2 files changed, 2 insertions(+) diff --git a/youtube_transcript_api/test/test_cli.py b/youtube_transcript_api/test/test_cli.py index 78c23c37..56a8abe7 100644 --- a/youtube_transcript_api/test/test_cli.py +++ b/youtube_transcript_api/test/test_cli.py @@ -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) diff --git a/youtube_transcript_api/test/test_formatters.py b/youtube_transcript_api/test/test_formatters.py index 28329184..084e13ec 100644 --- a/youtube_transcript_api/test/test_formatters.py +++ b/youtube_transcript_api/test/test_formatters.py @@ -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() From d2c92655338ead9594bee4e30a46e8783597b8ef Mon Sep 17 00:00:00 2001 From: Ahmet Kaya Date: Fri, 8 May 2026 20:38:57 +0300 Subject: [PATCH 4/5] run poe format --- youtube_transcript_api/_transcripts.py | 9 ++------- youtube_transcript_api/test/test_api.py | 4 ++-- youtube_transcript_api/test/test_cli.py | 2 +- youtube_transcript_api/test/test_formatters.py | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/youtube_transcript_api/_transcripts.py b/youtube_transcript_api/_transcripts.py index 14b5b07e..f794f281 100644 --- a/youtube_transcript_api/_transcripts.py +++ b/youtube_transcript_api/_transcripts.py @@ -178,7 +178,7 @@ def translate(self, language_code: str) -> "Transcript": self._translation_languages_dict[language_code], language_code, True, - [] + [], ) @@ -358,12 +358,7 @@ def __init__(self, http_client: Session, proxy_config: Optional[ProxyConfig]): def fetch(self, video_id: str) -> TranscriptList: captions_json, title = self._fetch_video_data(video_id) - return TranscriptList.build( - self._http_client, - video_id, - captions_json, - title - ) + return TranscriptList.build(self._http_client, video_id, captions_json, title) def _fetch_video_data(self, video_id: str, try_number: int = 0) -> tuple[Dict, str]: try: diff --git a/youtube_transcript_api/test/test_api.py b/youtube_transcript_api/test/test_api.py index 0c364244..81f3f2b9 100644 --- a/youtube_transcript_api/test/test_api.py +++ b/youtube_transcript_api/test/test_api.py @@ -72,7 +72,7 @@ def setUp(self): language_code="en", is_generated=False, video_id="GJLlxj_dtq8", - title="Surface Go Review - It’s Awesome" + title="Surface Go Review - It’s Awesome", ) self.ref_transcript_raw = self.ref_transcript.to_raw_data() @@ -114,7 +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" + content_type="application/json", ) transcript = YouTubeTranscriptApi().fetch("GJLlxj_dtq8") diff --git a/youtube_transcript_api/test/test_cli.py b/youtube_transcript_api/test/test_cli.py index 56a8abe7..cf3a526c 100644 --- a/youtube_transcript_api/test/test_cli.py +++ b/youtube_transcript_api/test/test_cli.py @@ -41,7 +41,7 @@ def setUp(self): language_code="en", is_generated=True, video_id="GJLlxj_dtq8", - title='Video Title' + title="Video Title", ) ) self.transcript_mock.translate = MagicMock(return_value=self.transcript_mock) diff --git a/youtube_transcript_api/test/test_formatters.py b/youtube_transcript_api/test/test_formatters.py index 084e13ec..633aebfe 100644 --- a/youtube_transcript_api/test/test_formatters.py +++ b/youtube_transcript_api/test/test_formatters.py @@ -31,7 +31,7 @@ def setUp(self): language_code="en", is_generated=True, video_id="12345", - title="Video Title" + title="Video Title", ) self.transcripts = [self.transcript, self.transcript] self.transcript_raw = self.transcript.to_raw_data() From cf62ed9f1624ed9cd64a2229c2043fefd5e9fd55 Mon Sep 17 00:00:00 2001 From: Ahmet Kaya Date: Fri, 8 May 2026 20:47:51 +0300 Subject: [PATCH 5/5] fix: update type hint for _fetch_video_data to use Tuple --- youtube_transcript_api/_transcripts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/youtube_transcript_api/_transcripts.py b/youtube_transcript_api/_transcripts.py index f794f281..a14104a5 100644 --- a/youtube_transcript_api/_transcripts.py +++ b/youtube_transcript_api/_transcripts.py @@ -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 @@ -360,7 +360,7 @@ def fetch(self, video_id: str) -> TranscriptList: captions_json, title = self._fetch_video_data(video_id) return TranscriptList.build(self._http_client, video_id, captions_json, title) - def _fetch_video_data(self, video_id: str, try_number: int = 0) -> tuple[Dict, str]: + 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)