u-transcript-max 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. u_transcript_max-0.1.0a1.dist-info/METADATA +67 -0
  2. u_transcript_max-0.1.0a1.dist-info/RECORD +77 -0
  3. u_transcript_max-0.1.0a1.dist-info/WHEEL +4 -0
  4. u_transcript_max-0.1.0a1.dist-info/entry_points.txt +2 -0
  5. u_transcript_max-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  6. utmax/__init__.py +651 -0
  7. utmax/_version.py +1 -0
  8. utmax/adapters/__init__.py +1 -0
  9. utmax/adapters/downloader.py +479 -0
  10. utmax/adapters/ffmpeg.py +174 -0
  11. utmax/adapters/files.py +130 -0
  12. utmax/adapters/http.py +285 -0
  13. utmax/adapters/innertube.py +207 -0
  14. utmax/adapters/providers/__init__.py +45 -0
  15. utmax/adapters/providers/base.py +177 -0
  16. utmax/adapters/providers/claude.py +108 -0
  17. utmax/adapters/providers/gemini.py +133 -0
  18. utmax/adapters/providers/openai.py +169 -0
  19. utmax/adapters/providers/openrouter.py +59 -0
  20. utmax/adapters/watch_page.py +73 -0
  21. utmax/client.py +300 -0
  22. utmax/compat/__init__.py +134 -0
  23. utmax/compat/_api.py +203 -0
  24. utmax/compat/_bridge.py +212 -0
  25. utmax/compat/_errors.py +300 -0
  26. utmax/compat/_settings.py +13 -0
  27. utmax/compat/_transcripts.py +440 -0
  28. utmax/compat/formatters.py +241 -0
  29. utmax/compat/proxies.py +129 -0
  30. utmax/core/__init__.py +6 -0
  31. utmax/core/bilingual.py +107 -0
  32. utmax/core/browse.py +397 -0
  33. utmax/core/captions.py +251 -0
  34. utmax/core/clients.py +113 -0
  35. utmax/core/downloads.py +165 -0
  36. utmax/core/filenames.py +298 -0
  37. utmax/core/formats.py +198 -0
  38. utmax/core/ids.py +210 -0
  39. utmax/core/languages.py +371 -0
  40. utmax/core/media/__init__.py +7 -0
  41. utmax/core/media/boxes.py +266 -0
  42. utmax/core/media/fmp4.py +394 -0
  43. utmax/core/media/moov.py +299 -0
  44. utmax/core/media/mux.py +423 -0
  45. utmax/core/media/progressive.py +267 -0
  46. utmax/core/media/tables.py +129 -0
  47. utmax/core/media/tx3g.py +191 -0
  48. utmax/core/playability.py +62 -0
  49. utmax/core/player.py +90 -0
  50. utmax/core/retry.py +45 -0
  51. utmax/core/segmentation.py +104 -0
  52. utmax/core/selection.py +92 -0
  53. utmax/core/streams.py +264 -0
  54. utmax/core/translate/__init__.py +1 -0
  55. utmax/core/translate/batching.py +157 -0
  56. utmax/core/translate/data/protocol.json +17 -0
  57. utmax/core/translate/data/request.schema.json +49 -0
  58. utmax/core/translate/data/response.schema.json +19 -0
  59. utmax/core/translate/data/system_prompt.txt +21 -0
  60. utmax/core/translate/protocol.py +101 -0
  61. utmax/core/translate/spec.py +49 -0
  62. utmax/core/ytdata.py +32 -0
  63. utmax/errors.py +523 -0
  64. utmax/mcp/__init__.py +30 -0
  65. utmax/mcp/__main__.py +6 -0
  66. utmax/mcp/config.py +56 -0
  67. utmax/mcp/server.py +626 -0
  68. utmax/models.py +499 -0
  69. utmax/providers.py +36 -0
  70. utmax/py.typed +0 -0
  71. utmax/services/__init__.py +1 -0
  72. utmax/services/bulk.py +454 -0
  73. utmax/services/collections.py +183 -0
  74. utmax/services/download.py +375 -0
  75. utmax/services/transcripts.py +87 -0
  76. utmax/services/translation.py +211 -0
  77. utmax/transport.py +81 -0
@@ -0,0 +1,300 @@
1
+ """The exceptions of youtube-transcript-api 1.2.4, with its messages word for word.
2
+
3
+ Code that catches these classes, reads their attributes or inspects ``str(error)`` keeps
4
+ working. utmax raises them from its own errors (see ``utmax.compat._bridge``); the original
5
+ utmax error is the ``__cause__``. ``TooManyRequests``, ``NoTranscriptAvailable`` and
6
+ ``CookiesInvalid`` are the names version 0.6 used.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from collections.abc import Iterable
13
+ from typing import TYPE_CHECKING, Self
14
+
15
+ from utmax.compat._settings import WATCH_URL
16
+ from utmax.compat.proxies import GenericProxyConfig, ProxyConfig, WebshareProxyConfig
17
+
18
+ if TYPE_CHECKING:
19
+ from utmax.compat._transcripts import TranscriptList
20
+
21
+ __all__ = [
22
+ "WATCH_URL",
23
+ "AgeRestricted",
24
+ "CookieError",
25
+ "CookieInvalid",
26
+ "CookiePathInvalid",
27
+ "CookiesInvalid",
28
+ "CouldNotRetrieveTranscript",
29
+ "FailedToCreateConsentCookie",
30
+ "GenericProxyConfig",
31
+ "InvalidVideoId",
32
+ "IpBlocked",
33
+ "NoTranscriptAvailable",
34
+ "NoTranscriptFound",
35
+ "NotTranslatable",
36
+ "PoTokenRequired",
37
+ "ProxyConfig",
38
+ "RequestBlocked",
39
+ "TooManyRequests",
40
+ "TranscriptsDisabled",
41
+ "TranslationLanguageNotAvailable",
42
+ "VideoUnavailable",
43
+ "VideoUnplayable",
44
+ "WebshareProxyConfig",
45
+ "YouTubeDataUnparsable",
46
+ "YouTubeRequestFailed",
47
+ "YouTubeTranscriptApiException",
48
+ ]
49
+
50
+
51
+ class YouTubeTranscriptApiException(Exception):
52
+ pass
53
+
54
+
55
+ class CookieError(YouTubeTranscriptApiException):
56
+ pass
57
+
58
+
59
+ class CookiePathInvalid(CookieError):
60
+ def __init__(self, cookie_path: str | os.PathLike[str]) -> None:
61
+ super().__init__(f"Can't load the provided cookie file: {cookie_path}")
62
+
63
+
64
+ class CookieInvalid(CookieError):
65
+ def __init__(self, cookie_path: str | os.PathLike[str]) -> None:
66
+ super().__init__(f"The cookies provided are not valid (may have expired): {cookie_path}")
67
+
68
+
69
+ class CouldNotRetrieveTranscript(YouTubeTranscriptApiException):
70
+ """
71
+ Raised if a transcript could not be retrieved.
72
+ """
73
+
74
+ ERROR_MESSAGE = "\nCould not retrieve a transcript for the video {video_url}!"
75
+ CAUSE_MESSAGE_INTRO = " This is most likely caused by:\n\n{cause}"
76
+ CAUSE_MESSAGE = ""
77
+ GITHUB_REFERRAL = (
78
+ "\n\nIf you are sure that the described cause is not responsible for this error "
79
+ "and that a transcript should be retrievable, please create an issue at "
80
+ "https://github.com/jdepoix/youtube-transcript-api/issues. "
81
+ "Please add which version of youtube_transcript_api you are using "
82
+ "and provide the information needed to replicate the error. "
83
+ "Also make sure that there are no open issues which already describe your problem!"
84
+ )
85
+
86
+ def __init__(self, video_id: str) -> None:
87
+ self.video_id = video_id
88
+ super().__init__()
89
+
90
+ def _build_error_message(self) -> str:
91
+ error_message = self.ERROR_MESSAGE.format(
92
+ video_url=WATCH_URL.format(video_id=self.video_id)
93
+ )
94
+
95
+ cause = self.cause
96
+ if cause:
97
+ error_message += self.CAUSE_MESSAGE_INTRO.format(cause=cause) + self.GITHUB_REFERRAL
98
+
99
+ return error_message
100
+
101
+ @property
102
+ def cause(self) -> str:
103
+ return self.CAUSE_MESSAGE
104
+
105
+ def __str__(self) -> str:
106
+ return self._build_error_message()
107
+
108
+
109
+ class YouTubeDataUnparsable(CouldNotRetrieveTranscript):
110
+ CAUSE_MESSAGE = (
111
+ "The data required to fetch the transcript is not parsable. This should "
112
+ "not happen, please open an issue (make sure to include the video ID)!"
113
+ )
114
+
115
+
116
+ class YouTubeRequestFailed(CouldNotRetrieveTranscript):
117
+ CAUSE_MESSAGE = "Request to YouTube failed: {reason}"
118
+
119
+ def __init__(self, video_id: str, http_error: object) -> None:
120
+ self.reason = str(http_error)
121
+ super().__init__(video_id)
122
+
123
+ @property
124
+ def cause(self) -> str:
125
+ return self.CAUSE_MESSAGE.format(
126
+ reason=self.reason,
127
+ )
128
+
129
+
130
+ class VideoUnplayable(CouldNotRetrieveTranscript):
131
+ CAUSE_MESSAGE = "The video is unplayable for the following reason: {reason}"
132
+ SUBREASON_MESSAGE = "\n\nAdditional Details:\n{sub_reasons}"
133
+
134
+ def __init__(self, video_id: str, reason: str | None, sub_reasons: list[str]) -> None:
135
+ self.reason = reason
136
+ self.sub_reasons = sub_reasons
137
+ super().__init__(video_id)
138
+
139
+ @property
140
+ def cause(self) -> str:
141
+ reason = "No reason specified!" if self.reason is None else self.reason
142
+ if self.sub_reasons:
143
+ sub_reasons = "\n".join(f" - {sub_reason}" for sub_reason in self.sub_reasons)
144
+ reason = f"{reason}{self.SUBREASON_MESSAGE.format(sub_reasons=sub_reasons)}"
145
+ return self.CAUSE_MESSAGE.format(
146
+ reason=reason,
147
+ )
148
+
149
+
150
+ class VideoUnavailable(CouldNotRetrieveTranscript):
151
+ CAUSE_MESSAGE = "The video is no longer available"
152
+
153
+
154
+ class InvalidVideoId(CouldNotRetrieveTranscript):
155
+ CAUSE_MESSAGE = (
156
+ "You provided an invalid video id. Make sure you are using the video id and NOT the url!\n\n"
157
+ 'Do NOT run: `YouTubeTranscriptApi().fetch("https://www.youtube.com/watch?v=1234")`\n'
158
+ 'Instead run: `YouTubeTranscriptApi().fetch("1234")`'
159
+ )
160
+
161
+
162
+ class RequestBlocked(CouldNotRetrieveTranscript):
163
+ BASE_CAUSE_MESSAGE = (
164
+ "YouTube is blocking requests from your IP. This usually is due to one of the "
165
+ "following reasons:\n"
166
+ "- You have done too many requests and your IP has been blocked by YouTube\n"
167
+ "- You are doing requests from an IP belonging to a cloud provider (like AWS, "
168
+ "Google Cloud Platform, Azure, etc.). Unfortunately, most IPs from cloud "
169
+ "providers are blocked by YouTube.\n\n"
170
+ )
171
+ CAUSE_MESSAGE = (
172
+ f"{BASE_CAUSE_MESSAGE}"
173
+ "There are two things you can do to work around this:\n"
174
+ '1. Use proxies to hide your IP address, as explained in the "Working around '
175
+ 'IP bans" section of the README '
176
+ "(https://github.com/jdepoix/youtube-transcript-api"
177
+ "?tab=readme-ov-file"
178
+ "#working-around-ip-bans-requestblocked-or-ipblocked-exception).\n"
179
+ "2. (NOT RECOMMENDED) If you authenticate your requests using cookies, you "
180
+ "will be able to continue doing requests for a while. However, YouTube will "
181
+ "eventually permanently ban the account that you have used to authenticate "
182
+ "with! So only do this if you don't mind your account being banned!"
183
+ )
184
+ WITH_GENERIC_PROXY_CAUSE_MESSAGE = (
185
+ "YouTube is blocking your requests, despite you using proxies. Keep in mind "
186
+ "that a proxy is just a way to hide your real IP behind the IP of that proxy, "
187
+ "but there is no guarantee that the IP of that proxy won't be blocked as "
188
+ "well.\n\n"
189
+ "The only truly reliable way to prevent IP blocks is rotating through a large "
190
+ "pool of residential IPs, by using a provider like Webshare "
191
+ "(https://www.webshare.io/?referral_code=w0xno53eb50g), which provides you "
192
+ "with a pool of >30M residential IPs (make sure to purchase "
193
+ '"Residential" proxies, NOT "Proxy Server" or "Static Residential"!).\n\n'
194
+ "You will find more information on how to easily integrate Webshare here: "
195
+ "https://github.com/jdepoix/youtube-transcript-api"
196
+ "?tab=readme-ov-file#using-webshare"
197
+ )
198
+ WITH_WEBSHARE_PROXY_CAUSE_MESSAGE = (
199
+ "YouTube is blocking your requests, despite you using Webshare proxies. "
200
+ 'Please make sure that you have purchased "Residential" proxies and '
201
+ 'NOT "Proxy Server" or "Static Residential", as those won\'t work as '
202
+ 'reliably! The free tier also uses "Proxy Server" and will NOT work!\n\n'
203
+ 'The only reliable option is using "Residential" proxies (not "Static '
204
+ 'Residential"), as this allows you to rotate through a pool of over 30M IPs, '
205
+ "which means you will always find an IP that hasn't been blocked by YouTube "
206
+ "yet!\n\n"
207
+ "You can support the development of this open source project by making your "
208
+ "Webshare purchases through this affiliate link: "
209
+ "https://www.webshare.io/?referral_code=w0xno53eb50g \n\n"
210
+ "Thank you for your support! <3"
211
+ )
212
+
213
+ def __init__(self, video_id: str) -> None:
214
+ self._proxy_config: ProxyConfig | None = None
215
+ super().__init__(video_id)
216
+
217
+ def with_proxy_config(self, proxy_config: ProxyConfig | None) -> Self:
218
+ self._proxy_config = proxy_config
219
+ return self
220
+
221
+ @property
222
+ def cause(self) -> str:
223
+ if isinstance(self._proxy_config, WebshareProxyConfig):
224
+ return self.WITH_WEBSHARE_PROXY_CAUSE_MESSAGE
225
+ if isinstance(self._proxy_config, GenericProxyConfig):
226
+ return self.WITH_GENERIC_PROXY_CAUSE_MESSAGE
227
+ return super().cause
228
+
229
+
230
+ class IpBlocked(RequestBlocked):
231
+ CAUSE_MESSAGE = (
232
+ f"{RequestBlocked.BASE_CAUSE_MESSAGE}"
233
+ 'Ways to work around this are explained in the "Working around IP '
234
+ 'bans" section of the README (https://github.com/jdepoix/youtube-transcript-api'
235
+ "?tab=readme-ov-file"
236
+ "#working-around-ip-bans-requestblocked-or-ipblocked-exception).\n"
237
+ )
238
+
239
+
240
+ class TranscriptsDisabled(CouldNotRetrieveTranscript):
241
+ CAUSE_MESSAGE = "Subtitles are disabled for this video"
242
+
243
+
244
+ class AgeRestricted(CouldNotRetrieveTranscript):
245
+ CAUSE_MESSAGE = (
246
+ "This video is age-restricted. Therefore, you are unable to retrieve "
247
+ "transcripts for it without authenticating yourself.\n\n"
248
+ "Unfortunately, Cookie Authentication is temporarily unsupported in "
249
+ "youtube-transcript-api, as recent changes in YouTube's API broke the previous "
250
+ "implementation. I will do my best to re-implement it as soon as possible."
251
+ )
252
+
253
+
254
+ class NotTranslatable(CouldNotRetrieveTranscript):
255
+ CAUSE_MESSAGE = "The requested language is not translatable"
256
+
257
+
258
+ class TranslationLanguageNotAvailable(CouldNotRetrieveTranscript):
259
+ CAUSE_MESSAGE = "The requested translation language is not available"
260
+
261
+
262
+ class FailedToCreateConsentCookie(CouldNotRetrieveTranscript):
263
+ CAUSE_MESSAGE = "Failed to automatically give consent to saving cookies"
264
+
265
+
266
+ class NoTranscriptFound(CouldNotRetrieveTranscript):
267
+ CAUSE_MESSAGE = (
268
+ "No transcripts were found for any of the requested language codes: {requested_language_codes}\n\n"
269
+ "{transcript_data}"
270
+ )
271
+
272
+ def __init__(
273
+ self,
274
+ video_id: str,
275
+ requested_language_codes: Iterable[str],
276
+ transcript_data: TranscriptList,
277
+ ) -> None:
278
+ self._requested_language_codes = requested_language_codes
279
+ self._transcript_data = transcript_data
280
+ super().__init__(video_id)
281
+
282
+ @property
283
+ def cause(self) -> str:
284
+ return self.CAUSE_MESSAGE.format(
285
+ requested_language_codes=self._requested_language_codes,
286
+ transcript_data=str(self._transcript_data),
287
+ )
288
+
289
+
290
+ class PoTokenRequired(CouldNotRetrieveTranscript):
291
+ CAUSE_MESSAGE = (
292
+ "The requested video cannot be retrieved without a PO Token. If this happens, "
293
+ "please open a GitHub issue!"
294
+ )
295
+
296
+
297
+ # The names youtube-transcript-api 0.6 used for these errors.
298
+ TooManyRequests = IpBlocked
299
+ NoTranscriptAvailable = TranscriptsDisabled
300
+ CookiesInvalid = CookieInvalid
@@ -0,0 +1,13 @@
1
+ """URL templates and the InnerTube context of youtube-transcript-api 1.2.4.
2
+
3
+ utmax builds its own requests (see ``utmax.core.clients``); these values only keep code that
4
+ reads them working.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__ = ["INNERTUBE_API_URL", "INNERTUBE_CONTEXT", "WATCH_URL"]
10
+
11
+ WATCH_URL = "https://www.youtube.com/watch?v={video_id}"
12
+ INNERTUBE_API_URL = "https://www.youtube.com/youtubei/v1/player?key={api_key}"
13
+ INNERTUBE_CONTEXT = {"client": {"clientName": "ANDROID", "clientVersion": "20.10.38"}}