Showing preview only (2,212K chars total). Download the full file or copy to clipboard to get everything.
Repository: jdepoix/youtube-transcript-api
Branch: master
Commit: a6352e901974
Files: 39
Total size: 2.1 MB
Directory structure:
gitextract_aofas8tg/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── pyproject.toml
└── youtube_transcript_api/
├── __init__.py
├── __main__.py
├── _api.py
├── _cli.py
├── _errors.py
├── _settings.py
├── _transcripts.py
├── formatters.py
├── proxies.py
├── py.typed
└── test/
├── __init__.py
├── assets/
│ ├── __init__.py
│ ├── transcript.xml.static
│ ├── youtube.html.static
│ ├── youtube.innertube.json.static
│ ├── youtube_age_restricted.innertube.json.static
│ ├── youtube_altered_user_agent.innertube.json.static
│ ├── youtube_consent_page.html.static
│ ├── youtube_consent_page_invalid.html.static
│ ├── youtube_po_token_required.innertube.json.static
│ ├── youtube_request_blocked.innertube.json.static
│ ├── youtube_too_many_requests.html.static
│ ├── youtube_transcripts_disabled.innertube.json.static
│ ├── youtube_transcripts_disabled2.innertube.json.static
│ ├── youtube_unplayable.innertube.json.static
│ ├── youtube_video_unavailable.innertube.json.static
│ └── youtube_ww1_nl_en.innertube.json.static
├── test_api.py
├── test_cli.py
├── test_formatters.py
└── test_proxies.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: jdepoix
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BAENLEW8VUJ6G&source=url
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
DO NOT DELETE THIS! Please take the time to fill this out properly. I am not able to help you if I do not know what you are executing and what error messages you are getting. If you are having problems with a specific video make sure to **include the video id**.
# To Reproduce
Steps to reproduce the behavior:
### What code / cli command are you executing?
For example: I am running
```
YouTubeTranscriptApi().fetch() ...
```
### Which Python version are you using?
Python x.y
### Which version of youtube-transcript-api are you using?
youtube-transcript-api x.y.z
# Expected behavior
Describe what you expected to happen.
For example: I expected to receive the english transcript
# Actual behaviour
Describe what is happening instead of the **Expected behavior**. Add **error messages** if there are any.
For example: Instead I received the following error message:
```
# ... error message ...
```
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of how you want youtube-transcript-api to solve your problem.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the feature request here. If you have any additional technical information which could be relevant for the implementation, feel free to share them here.
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [ "master" ]
tags:
- '**'
pull_request:
jobs:
static-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install poetry poethepoet
poetry install --only dev
- name: Format
run: poe ci-format
- name: Lint
run: poe lint
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install poetry poethepoet
poetry install --with test
- name: Run tests
run: |
poe ci-test
- name: Report intermediate coverage report
uses: coverallsapp/github-action@v2
with:
file: coverage.xml
format: cobertura
flag-name: run-python-${{ matrix.python-version }}
parallel: true
coverage:
needs: test
runs-on: ubuntu-latest
steps:
- name: Finalize coverage report
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
carryforward: "run-python-3.8,run-python-3.9,run-python-3.10,run-python-3.11,run-python-3.12,run-python-3.13,run-python-3.14"
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install poetry poethepoet
poetry install --with test
- name: Check coverage
run: poe coverage
publish:
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: [coverage, static-checks]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install poetry
poetry install
- name: Build
run: poetry build
- name: Publish
run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}
================================================
FILE: .gitignore
================================================
.idea
.venv
virtualenv
*.pyc
dist
build
*.egg-info
upload_new_version.sh
.coverage
coverage.xml
.DS_STORE
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Jonas Depoix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<h1 align="center">
✨ YouTube Transcript API ✨
</h1>
<p align="center">
<a href="https://github.com/sponsors/jdepoix">
<img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86" alt="Sponsor">
</a>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BAENLEW8VUJ6G&source=url">
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate">
</a>
<a href="https://github.com/jdepoix/youtube-transcript-api/actions">
<img src="https://github.com/jdepoix/youtube-transcript-api/actions/workflows/ci.yml/badge.svg?branch=master" alt="Build Status">
</a>
<a href="https://coveralls.io/github/jdepoix/youtube-transcript-api?branch=master">
<img src="https://coveralls.io/repos/github/jdepoix/youtube-transcript-api/badge.svg?branch=master" alt="Coverage Status">
</a>
<a href="http://opensource.org/licenses/MIT">
<img src="http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat" alt="MIT license">
</a>
<a href="https://pypi.org/project/youtube-transcript-api/">
<img src="https://img.shields.io/pypi/v/youtube-transcript-api.svg" alt="Current Version">
</a>
<a href="https://pypi.org/project/youtube-transcript-api/">
<img src="https://img.shields.io/pypi/pyversions/youtube-transcript-api.svg" alt="Supported Python Versions">
</a>
</p>
<p align="center">
<b>This is a python API which allows you to retrieve the transcript/subtitles for a given YouTube video. It also works for automatically generated subtitles, supports translating subtitles and it does not require a headless browser, like other selenium based solutions do!</b>
</p>
<p align="center">
Maintenance of this project is made possible by all the <a href="https://github.com/jdepoix/youtube-transcript-api/graphs/contributors">contributors</a> and <a href="https://github.com/sponsors/jdepoix">sponsors</a>. If you'd like to sponsor this project and have your avatar or company logo appear below <a href="https://github.com/sponsors/jdepoix">click here</a>. 💖
</p>
<p align="center">
<a href="https://www.searchapi.io">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://www.searchapi.io/press/v1/svg/searchapi_logo_white_h.svg">
<source media="(prefers-color-scheme: light)" srcset="https://www.searchapi.io/press/v1/svg/searchapi_logo_black_h.svg">
<img alt="SearchAPI" src="https://www.searchapi.io/press/v1/svg/searchapi_logo_black_h.svg" height="40px" style="vertical-align: middle;">
</picture>
</a>
<a href="https://supadata.ai">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://supadata.ai/logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://supadata.ai/logo-light.svg">
<img alt="supadata" src="https://supadata.ai/logo-light.svg" height="40px">
</picture>
</a>
<a href="https://www.dumplingai.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://www.dumplingai.com/logos/logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://www.dumplingai.com/logos/logo-light.svg">
<img alt="Dumpling AI" src="https://www.dumplingai.com/logos/logo-light.svg" height="40px" style="vertical-align: middle;">
</picture>
</a>
</p>
## Install
It is recommended to [install this module by using pip](https://pypi.org/project/youtube-transcript-api/):
```
pip install youtube-transcript-api
```
You can either integrate this module [into an existing application](#api) or just use it via a [CLI](#cli).
## API
The easiest way to get a transcript for a given video is to execute:
```python
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
ytt_api.fetch(video_id)
```
> **Note:** By default, this will try to access the English transcript of the video. If your video has a different
> language, or you are interested in fetching a transcript in a different language, please read the section below.
> **Note:** Pass in the video ID, NOT the video URL. For a video with the URL `https://www.youtube.com/watch?v=12345`
> the ID is `12345`.
This will return a `FetchedTranscript` object looking somewhat like this:
```python
FetchedTranscript(
snippets=[
FetchedTranscriptSnippet(
text="Hey there",
start=0.0,
duration=1.54,
),
FetchedTranscriptSnippet(
text="how are you",
start=1.54,
duration=4.16,
),
# ...
],
video_id="12345",
language="English",
language_code="en",
is_generated=False,
)
```
This object implements most interfaces of a `List`:
```python
ytt_api = YouTubeTranscriptApi()
fetched_transcript = ytt_api.fetch(video_id)
# is iterable
for snippet in fetched_transcript:
print(snippet.text)
# indexable
last_snippet = fetched_transcript[-1]
# provides a length
snippet_count = len(fetched_transcript)
```
If you prefer to handle the raw transcript data you can call `fetched_transcript.to_raw_data()`, which will return
a list of dictionaries:
```python
[
{
'text': 'Hey there',
'start': 0.0,
'duration': 1.54
},
{
'text': 'how are you',
'start': 1.54
'duration': 4.16
},
# ...
]
```
### Retrieve different languages
You can add the `languages` param if you want to make sure the transcripts are retrieved in your desired language
(it defaults to english).
```python
YouTubeTranscriptApi().fetch(video_id, languages=['de', 'en'])
```
It's a list of language codes in a descending priority. In this example it will first try to fetch the german
transcript (`'de'`) and then fetch the english transcript (`'en'`) if it fails to do so. If you want to find out
which languages are available first, [have a look at `list()`](#list-available-transcripts).
If you only want one language, you still need to format the `languages` argument as a list
```python
YouTubeTranscriptApi().fetch(video_id, languages=['de'])
```
### Preserve formatting
You can also add `preserve_formatting=True` if you'd like to keep HTML formatting elements such as `<i>` (italics)
and `<b>` (bold).
```python
YouTubeTranscriptApi().fetch(video_ids, languages=['de', 'en'], preserve_formatting=True)
```
### List available transcripts
If you want to list all transcripts which are available for a given video you can call:
```python
ytt_api = YouTubeTranscriptApi()
transcript_list = ytt_api.list(video_id)
```
This will return a `TranscriptList` object which is iterable and provides methods to filter the list of transcripts for
specific languages and types, like:
```python
transcript = transcript_list.find_transcript(['de', 'en'])
```
By default this module always chooses manually created transcripts over automatically created ones, if a transcript in
the requested language is available both manually created and generated. The `TranscriptList` allows you to bypass this
default behaviour by searching for specific transcript types:
```python
# filter for manually created transcripts
transcript = transcript_list.find_manually_created_transcript(['de', 'en'])
# or automatically generated ones
transcript = transcript_list.find_generated_transcript(['de', 'en'])
```
The methods `find_generated_transcript`, `find_manually_created_transcript`, `find_transcript` return `Transcript`
objects. They contain metadata regarding the transcript:
```python
print(
transcript.video_id,
transcript.language,
transcript.language_code,
# whether it has been manually created or generated by YouTube
transcript.is_generated,
# whether this transcript can be translated or not
transcript.is_translatable,
# a list of languages the transcript can be translated to
transcript.translation_languages,
)
```
and provide the method, which allows you to fetch the actual transcript data:
```python
transcript.fetch()
```
This returns a `FetchedTranscript` object, just like `YouTubeTranscriptApi().fetch()` does.
### Translate transcript
YouTube has a feature which allows you to automatically translate subtitles. This module also makes it possible to
access this feature. To do so `Transcript` objects provide a `translate()` method, which returns a new translated
`Transcript` object:
```python
transcript = transcript_list.find_transcript(['en'])
translated_transcript = transcript.translate('de')
print(translated_transcript.fetch())
```
### By example
```python
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
# retrieve the available transcripts
transcript_list = ytt_api.list('video_id')
# iterate over all available transcripts
for transcript in transcript_list:
# the Transcript object provides metadata properties
print(
transcript.video_id,
transcript.language,
transcript.language_code,
# whether it has been manually created or generated by YouTube
transcript.is_generated,
# whether this transcript can be translated or not
transcript.is_translatable,
# a list of languages the transcript can be translated to
transcript.translation_languages,
)
# fetch the actual transcript data
print(transcript.fetch())
# translating the transcript will return another transcript object
print(transcript.translate('en').fetch())
# you can also directly filter for the language you are looking for, using the transcript list
transcript = transcript_list.find_transcript(['de', 'en'])
# or just filter for manually created transcripts
transcript = transcript_list.find_manually_created_transcript(['de', 'en'])
# or automatically generated ones
transcript = transcript_list.find_generated_transcript(['de', 'en'])
```
## Working around IP bans (`RequestBlocked` or `IpBlocked` exception)
Unfortunately, YouTube has started blocking most IPs that are known to belong to cloud providers (like AWS, Google Cloud
Platform, Azure, etc.), which means you will most likely run into `RequestBlocked` or `IpBlocked` exceptions when
deploying your code to any cloud solutions. Same can happen to the IP of your self-hosted solution, if you are doing
too many requests. You can work around these IP bans using proxies. However, since YouTube will ban static proxies
after extended use, going for rotating residential proxies provide is the most reliable option.
There are different providers that offer rotating residential proxies, but after testing different
offerings I have found [Webshare](https://www.webshare.io/?referral_code=w0xno53eb50g) to be the most reliable and have
therefore integrated it into this module, to make setting it up as easy as possible.
### Using [Webshare](https://www.webshare.io/?referral_code=w0xno53eb50g)
Once you have created a [Webshare account](https://www.webshare.io/?referral_code=w0xno53eb50g) and purchased a
"Residential" proxy package that suits your workload (make sure NOT to purchase "Proxy Server" or
"Static Residential"!), open the
[Webshare Proxy Settings](https://dashboard.webshare.io/proxy/settings?referral_code=w0xno53eb50g) to retrieve
your "Proxy Username" and "Proxy Password". Using this information you can initialize the `YouTubeTranscriptApi` as
follows:
```python
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import WebshareProxyConfig
ytt_api = YouTubeTranscriptApi(
proxy_config=WebshareProxyConfig(
proxy_username="<proxy-username>",
proxy_password="<proxy-password>",
)
)
# all requests done by ytt_api will now be proxied through Webshare
ytt_api.fetch(video_id)
```
Using the `WebshareProxyConfig` will default to using rotating residential proxies and requires no further
configuration.
You can also limit the pool of IPs that you will be rotating through to those located in specific countries. By
choosing locations that are close to the machine that is running your code, you can reduce latency. Also, this
can be used to work around location-based restrictions.
```python
ytt_api = YouTubeTranscriptApi(
proxy_config=WebshareProxyConfig(
proxy_username="<proxy-username>",
proxy_password="<proxy-password>",
filter_ip_locations=["de", "us"],
)
)
# Webshare will now only rotate through IPs located in Germany or the United States!
ytt_api.fetch(video_id)
```
You can find the
full list of available locations (and how many IPs are available in each location)
[here](https://www.webshare.io/features/proxy-locations?referral_code=w0xno53eb50g).
Note that [referral links are used here](https://www.webshare.io/?referral_code=w0xno53eb50g) and any purchases
made through these links will support this Open Source project (at no additional cost of course!), which is very much
appreciated! 💖😊🙏💖
However, you are of course free to integrate your own proxy solution using the `GenericProxyConfig` class, if you
prefer using another provider or want to implement your own solution, as covered by the following section.
### Using other Proxy solutions
Alternatively to using [Webshare](#using-webshare), you can set up any generic HTTP/HTTPS/SOCKS proxy using the
`GenericProxyConfig` class:
```python
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
ytt_api = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(
http_url="http://user:pass@my-custom-proxy.org:port",
https_url="https://user:pass@my-custom-proxy.org:port",
)
)
# all requests done by ytt_api will now be proxied using the defined proxy URLs
ytt_api.fetch(video_id)
```
Be aware that using a proxy doesn't guarantee that you won't be blocked, as YouTube can always block the IP of your
proxy! Therefore, you should always choose a solution that rotates through a pool of proxy addresses, if you want to
maximize reliability.
## Overwriting request defaults
When initializing a `YouTubeTranscriptApi` object, it will create a `requests.Session` which will be used for all
HTTP(S) request. This allows for caching cookies when retrieving multiple requests. However, you can optionally pass a
`requests.Session` object into its constructor, if you manually want to share cookies between different instances of
`YouTubeTranscriptApi`, overwrite defaults, set custom headers, specify SSL certificates, etc.
```python
from requests import Session
http_client = Session()
# set custom header
http_client.headers.update({"Accept-Encoding": "gzip, deflate"})
# set path to CA_BUNDLE file
http_client.verify = "/path/to/certfile"
ytt_api = YouTubeTranscriptApi(http_client=http_client)
ytt_api.fetch(video_id)
# share same Session between two instances of YouTubeTranscriptApi
ytt_api_2 = YouTubeTranscriptApi(http_client=http_client)
# now shares cookies with ytt_api
ytt_api_2.fetch(video_id)
```
## Cookie Authentication
Some videos are age restricted, so this module won't be able to access those videos without some sort of
authentication. Unfortunately, some recent changes to the YouTube API have broken the current implementation of cookie
based authentication, so this feature is currently not available.
## Using Formatters
Formatters are meant to be an additional layer of processing of the transcript you pass it. The goal is to convert a
`FetchedTranscript` object into a consistent string of a given "format". Such as a basic text (`.txt`) or even formats
that have a defined specification such as JSON (`.json`), WebVTT (`.vtt`), SRT (`.srt`), Comma-separated format
(`.csv`), etc...
The `formatters` submodule provides a few basic formatters, which can be used as is, or extended to your needs:
- JSONFormatter
- PrettyPrintFormatter
- TextFormatter
- WebVTTFormatter
- SRTFormatter
Here is how to import from the `formatters` module.
```python
# the base class to inherit from when creating your own formatter.
from youtube_transcript_api.formatters import Formatter
# some provided subclasses, each outputs a different string format.
from youtube_transcript_api.formatters import JSONFormatter
from youtube_transcript_api.formatters import TextFormatter
from youtube_transcript_api.formatters import WebVTTFormatter
from youtube_transcript_api.formatters import SRTFormatter
```
### Formatter Example
Let's say we wanted to retrieve a transcript and store it to a JSON file. That would look something like this:
```python
# your_custom_script.py
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.formatters import JSONFormatter
ytt_api = YouTubeTranscriptApi()
transcript = ytt_api.fetch(video_id)
formatter = JSONFormatter()
# .format_transcript(transcript) turns the transcript into a JSON string.
json_formatted = formatter.format_transcript(transcript)
# Now we can write it out to a file.
with open('your_filename.json', 'w', encoding='utf-8') as json_file:
json_file.write(json_formatted)
# Now should have a new JSON file that you can easily read back into Python.
```
**Passing extra keyword arguments**
Since JSONFormatter leverages `json.dumps()` you can also forward keyword arguments into
`.format_transcript(transcript)` such as making your file output prettier by forwarding the `indent=2` keyword argument.
```python
json_formatted = JSONFormatter().format_transcript(transcript, indent=2)
```
### Custom Formatter Example
You can implement your own formatter class. Just inherit from the `Formatter` base class and ensure you implement the
`format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str` and
`format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str` methods which should ultimately
return a string when called on your formatter instance.
```python
class MyCustomFormatter(Formatter):
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
# Do your custom work in here, but return a string.
return 'your processed output data as a string.'
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str:
# Do your custom work in here to format a list of transcripts, but return a string.
return 'your processed output data as a string.'
```
## CLI
Execute the CLI script using the video ids as parameters and the results will be printed out to the command line:
```
youtube_transcript_api <first_video_id> <second_video_id> ...
```
The CLI also gives you the option to provide a list of preferred languages:
```
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en
```
You can also specify if you want to exclude automatically generated or manually created subtitles:
```
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --exclude-generated
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --exclude-manually-created
```
If you would prefer to write it into a file or pipe it into another application, you can also output the results as
json using the following line:
```
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --format json > transcripts.json
```
Translating transcripts using the CLI is also possible:
```
youtube_transcript_api <first_video_id> <second_video_id> ... --languages en --translate de
```
If you are not sure which languages are available for a given video you can call, to list all available transcripts:
```
youtube_transcript_api --list-transcripts <first_video_id>
```
If a video's ID starts with a hyphen you'll have to mask the hyphen using `\` to prevent the CLI from mistaking it for
a argument name. For example to get the transcript for the video with the ID `-abc123` run:
```
youtube_transcript_api "\-abc123"
```
### Working around IP bans using the CLI
If you are running into `RequestBlocked` or `IpBlocked` errors, because YouTube blocks your IP, you can work around this
using residential proxies as explained in
[Working around IP bans](#working-around-ip-bans-requestblocked-or-ipblocked-exception). To use
[Webshare "Residential" proxies](https://www.webshare.io/?referral_code=w0xno53eb50g) through the CLI, you will have to
create a [Webshare account](https://www.webshare.io/?referral_code=w0xno53eb50g) and purchase a "Residential" proxy
package that suits your workload (make sure NOT to purchase "Proxy Server" or "Static Residential"!). Then you can use
the "Proxy Username" and "Proxy Password" which you can find in your
[Webshare Proxy Settings](https://dashboard.webshare.io/proxy/settings?referral_code=w0xno53eb50g), to run the following command:
```
youtube_transcript_api <first_video_id> <second_video_id> --webshare-proxy-username "username" --webshare-proxy-password "password"
```
If you prefer to use another proxy solution, you can set up a generic HTTP/HTTPS proxy using the following command:
```
youtube_transcript_api <first_video_id> <second_video_id> --http-proxy http://user:pass@domain:port --https-proxy https://user:pass@domain:port
```
### Cookie Authentication using the CLI
To authenticate using cookies through the CLI as explained in [Cookie Authentication](#cookie-authentication) run:
```
youtube_transcript_api <first_video_id> <second_video_id> --cookies /path/to/your/cookies.txt
```
## Warning
This code uses an undocumented part of the YouTube API, which is called by the YouTube web-client. So there is no
guarantee that it won't stop working tomorrow, if they change how things work. I will however do my best to make things
working again as soon as possible if that happens. So if it stops working, let me know!
## Contributing
To setup the project locally run the following (requires [poetry](https://python-poetry.org/docs/) to be installed):
```shell
poetry install --with test,dev
```
There's [poe](https://github.com/nat-n/poethepoet?tab=readme-ov-file#quick-start) tasks to run tests, coverage, the
linter and formatter (you'll need to pass all of those for the build to pass):
```shell
poe test
poe coverage
poe format
poe lint
```
If you just want to make sure that your code passes all the necessary checks to get a green build, you can simply run:
```shell
poe precommit
```
## Donations
If this project makes you happy by reducing your development time, you can make me happy by treating me to a cup of
coffee, or become a [Sponsor of this project](https://github.com/sponsors/jdepoix) :)
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BAENLEW8VUJ6G&source=url)
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "youtube-transcript-api"
version = "1.2.4"
description = "This is a python API which allows you to get the transcripts/subtitles for a given YouTube video. It also works for automatically generated subtitles, supports translating subtitles and it does not require a headless browser, like other selenium based solutions do!"
readme = "README.md"
license = "MIT"
authors = [
"Jonas Depoix <jonas.depoix@web.de>",
]
homepage = "https://github.com/jdepoix/youtube-transcript-api"
repository = "https://github.com/jdepoix/youtube-transcript-api"
keywords = [
"cli",
"subtitle",
"subtitles",
"transcript",
"transcripts",
"youtube",
"youtube-api",
"youtube-subtitles",
"youtube-transcripts",
]
classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
[tool.poetry.scripts]
youtube_transcript_api = "youtube_transcript_api.__main__:main"
[tool.poe.tasks]
test = "pytest youtube_transcript_api"
ci-test.shell = "coverage run -m pytest youtube_transcript_api && coverage xml"
coverage.shell = "coverage run -m pytest youtube_transcript_api && coverage report -m --fail-under=100"
format = "ruff format youtube_transcript_api"
ci-format = "ruff format youtube_transcript_api --check"
lint = "ruff check youtube_transcript_api"
precommit.shell = "poe format && poe lint && poe coverage"
[tool.poetry.dependencies]
python = ">=3.8,<3.15"
requests = "*"
defusedxml = "^0.7.1"
[tool.poetry.group.test]
optional = true
[tool.poetry.group.test.dependencies]
pytest = "^8.3.3"
coverage = "^7.6.1"
responses = "^0.26.0"
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.8"
[tool.coverage.run]
source = ["youtube_transcript_api"]
[tool.coverage.report]
omit = ["*/__main__.py", "youtube_transcript_api/test/*"]
exclude_lines = [
"pragma: no cover",
# Don't complain about missing debug-only code:
"def __unicode__",
"def __repr__",
"if self\\.debug",
# Don't complain if tests don't hit defensive assertion code:
"raise AssertionError",
"raise NotImplementedError",
# Don't complain if non-runnable code isn't run:
"if 0:",
"if __name__ == .__main__.:",
# Don't complain about empty stubs of abstract methods
"@abstractmethod",
"@abstractclassmethod",
"@abstractstaticmethod"
]
show_missing = true
================================================
FILE: youtube_transcript_api/__init__.py
================================================
# ruff: noqa: F401
from ._api import YouTubeTranscriptApi
from ._transcripts import (
TranscriptList,
Transcript,
FetchedTranscript,
FetchedTranscriptSnippet,
)
from ._errors import (
YouTubeTranscriptApiException,
CookieError,
CookiePathInvalid,
CookieInvalid,
TranscriptsDisabled,
NoTranscriptFound,
CouldNotRetrieveTranscript,
VideoUnavailable,
VideoUnplayable,
IpBlocked,
RequestBlocked,
NotTranslatable,
TranslationLanguageNotAvailable,
FailedToCreateConsentCookie,
YouTubeRequestFailed,
InvalidVideoId,
AgeRestricted,
YouTubeDataUnparsable,
PoTokenRequired,
)
__all__ = [
"YouTubeTranscriptApi",
"TranscriptList",
"Transcript",
"FetchedTranscript",
"FetchedTranscriptSnippet",
"YouTubeTranscriptApiException",
"CookieError",
"CookiePathInvalid",
"CookieInvalid",
"TranscriptsDisabled",
"NoTranscriptFound",
"CouldNotRetrieveTranscript",
"VideoUnavailable",
"VideoUnplayable",
"IpBlocked",
"RequestBlocked",
"NotTranslatable",
"TranslationLanguageNotAvailable",
"FailedToCreateConsentCookie",
"YouTubeRequestFailed",
"InvalidVideoId",
"AgeRestricted",
"YouTubeDataUnparsable",
"PoTokenRequired",
]
================================================
FILE: youtube_transcript_api/__main__.py
================================================
import sys
import logging
from ._cli import YouTubeTranscriptCli
def main():
logging.basicConfig()
print(YouTubeTranscriptCli(sys.argv[1:]).run())
if __name__ == "__main__":
main()
================================================
FILE: youtube_transcript_api/_api.py
================================================
from typing import Optional, Iterable
from requests import Session
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from .proxies import ProxyConfig
from ._transcripts import TranscriptListFetcher, FetchedTranscript, TranscriptList
class YouTubeTranscriptApi:
def __init__(
self,
proxy_config: Optional[ProxyConfig] = None,
http_client: Optional[Session] = None,
):
"""
Note on thread-safety: As this class will initialize a `requests.Session`
object, it is not thread-safe. Make sure to initialize an instance of
`YouTubeTranscriptApi` per thread, if used in a multi-threading scenario!
:param proxy_config: an optional ProxyConfig object, defining proxies used for
all network requests. This can be used to work around your IP being blocked
by YouTube, as described in the "Working around IP bans" section of the
README
(https://github.com/jdepoix/youtube-transcript-api?tab=readme-ov-file#working-around-ip-bans-requestblocked-or-ipblocked-exception)
:param http_client: You can optionally pass in a requests.Session object, if you
manually want to share cookies between different instances of
`YouTubeTranscriptApi`, overwrite defaults, specify SSL certificates, etc.
"""
http_client = Session() if http_client is None else http_client
http_client.headers.update({"Accept-Language": "en-US"})
# Cookie auth has been temporarily disabled, as it is not working properly with
# YouTube's most recent changes.
# if cookie_path is not None:
# http_client.cookies = _load_cookie_jar(cookie_path)
if proxy_config is not None:
http_client.proxies = proxy_config.to_requests_dict()
if proxy_config.prevent_keeping_connections_alive:
http_client.headers.update({"Connection": "close"})
if proxy_config.retries_when_blocked > 0:
retry_config = Retry(
total=proxy_config.retries_when_blocked,
status_forcelist=[429],
)
http_client.mount("http://", HTTPAdapter(max_retries=retry_config))
http_client.mount("https://", HTTPAdapter(max_retries=retry_config))
self._fetcher = TranscriptListFetcher(http_client, proxy_config=proxy_config)
def fetch(
self,
video_id: str,
languages: Iterable[str] = ("en",),
preserve_formatting: bool = False,
) -> FetchedTranscript:
"""
Retrieves the transcript for a single video. This is just a shortcut for
calling:
`YouTubeTranscriptApi().list(video_id).find_transcript(languages).fetch(preserve_formatting=preserve_formatting)`
:param video_id: the ID of the video you want to retrieve the transcript for.
Make sure that this is the actual ID, NOT the full URL to the video!
:param languages: A list of language codes in a descending priority. For
example, if this is set to ["de", "en"] it will first try to fetch the
german transcript (de) and then fetch the english transcript (en) if
it fails to do so. This defaults to ["en"].
:param preserve_formatting: whether to keep select HTML text formatting
"""
return (
self.list(video_id)
.find_transcript(languages)
.fetch(preserve_formatting=preserve_formatting)
)
def list(
self,
video_id: str,
) -> TranscriptList:
"""
Retrieves the list of transcripts which are available for a given video. It
returns a `TranscriptList` object which is iterable and provides methods to
filter the list of transcripts for specific languages. While iterating over
the `TranscriptList` the individual transcripts are represented by
`Transcript` objects, which provide metadata and can either be fetched by
calling `transcript.fetch()` or translated by calling `transcript.translate(
'en')`. Example:
```
ytt_api = YouTubeTranscriptApi()
# retrieve the available transcripts
transcript_list = ytt_api.list('video_id')
# iterate over all available transcripts
for transcript in transcript_list:
# the Transcript object provides metadata properties
print(
transcript.video_id,
transcript.language,
transcript.language_code,
# whether it has been manually created or generated by YouTube
transcript.is_generated,
# a list of languages the transcript can be translated to
transcript.translation_languages,
)
# fetch the actual transcript data
print(transcript.fetch())
# translating the transcript will return another transcript object
print(transcript.translate('en').fetch())
# you can also directly filter for the language you are looking for, using the transcript list
transcript = transcript_list.find_transcript(['de', 'en'])
# or just filter for manually created transcripts
transcript = transcript_list.find_manually_created_transcript(['de', 'en'])
# or automatically generated ones
transcript = transcript_list.find_generated_transcript(['de', 'en'])
```
:param video_id: the ID of the video you want to retrieve the transcript for.
Make sure that this is the actual ID, NOT the full URL to the video!
"""
return self._fetcher.fetch(video_id)
================================================
FILE: youtube_transcript_api/_cli.py
================================================
import argparse
from importlib.metadata import PackageNotFoundError, version
from typing import List
from .proxies import GenericProxyConfig, WebshareProxyConfig
from .formatters import FormatterLoader
from ._api import YouTubeTranscriptApi, FetchedTranscript, TranscriptList
class YouTubeTranscriptCli:
def __init__(self, args: List[str]):
self._args = args
def run(self) -> str:
parsed_args = self._parse_args()
if parsed_args.exclude_manually_created and parsed_args.exclude_generated:
return ""
proxy_config = None
if parsed_args.http_proxy != "" or parsed_args.https_proxy != "":
proxy_config = GenericProxyConfig(
http_url=parsed_args.http_proxy,
https_url=parsed_args.https_proxy,
)
if (
parsed_args.webshare_proxy_username is not None
or parsed_args.webshare_proxy_password is not None
):
proxy_config = WebshareProxyConfig(
proxy_username=parsed_args.webshare_proxy_username,
proxy_password=parsed_args.webshare_proxy_password,
)
transcripts = []
exceptions = []
ytt_api = YouTubeTranscriptApi(
proxy_config=proxy_config,
)
for video_id in parsed_args.video_ids:
try:
transcript_list = ytt_api.list(video_id)
if parsed_args.list_transcripts:
transcripts.append(transcript_list)
else:
transcripts.append(
self._fetch_transcript(
parsed_args,
transcript_list,
)
)
except Exception as exception:
exceptions.append(exception)
print_sections = [str(exception) for exception in exceptions]
if transcripts:
if parsed_args.list_transcripts:
print_sections.extend(
str(transcript_list) for transcript_list in transcripts
)
else:
print_sections.append(
FormatterLoader()
.load(parsed_args.format)
.format_transcripts(transcripts)
)
return "\n\n".join(print_sections)
def _fetch_transcript(
self,
parsed_args,
transcript_list: TranscriptList,
) -> FetchedTranscript:
if parsed_args.exclude_manually_created:
transcript = transcript_list.find_generated_transcript(
parsed_args.languages
)
elif parsed_args.exclude_generated:
transcript = transcript_list.find_manually_created_transcript(
parsed_args.languages
)
else:
transcript = transcript_list.find_transcript(parsed_args.languages)
if parsed_args.translate:
transcript = transcript.translate(parsed_args.translate)
return transcript.fetch()
def _get_version(self):
try:
return version("youtube-transcript-api")
except PackageNotFoundError:
return "unknown"
def _parse_args(self):
parser = argparse.ArgumentParser(
description=(
"This is a python API which allows you to get the transcripts/subtitles for a given YouTube video. "
"It also works for automatically generated subtitles and it does not require a headless browser, like "
"other selenium based solutions do!"
)
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s, version {self._get_version()}",
)
parser.add_argument(
"--list-transcripts",
action="store_const",
const=True,
default=False,
help="This will list the languages in which the given videos are available in.",
)
parser.add_argument(
"video_ids", nargs="+", type=str, help="List of YouTube video IDs."
)
parser.add_argument(
"--languages",
nargs="*",
default=[
"en",
],
type=str,
help=(
'A list of language codes in a descending priority. For example, if this is set to "de en" it will '
"first try to fetch the german transcript (de) and then fetch the english transcript (en) if it fails "
"to do so. As I can't provide a complete list of all working language codes with full certainty, you "
"may have to play around with the language codes a bit, to find the one which is working for you!"
),
)
parser.add_argument(
"--exclude-generated",
action="store_const",
const=True,
default=False,
help="If this flag is set transcripts which have been generated by YouTube will not be retrieved.",
)
parser.add_argument(
"--exclude-manually-created",
action="store_const",
const=True,
default=False,
help="If this flag is set transcripts which have been manually created will not be retrieved.",
)
parser.add_argument(
"--format",
type=str,
default="pretty",
choices=tuple(FormatterLoader.TYPES.keys()),
)
parser.add_argument(
"--translate",
default="",
help=(
"The language code for the language you want this transcript to be translated to. Use the "
"--list-transcripts feature to find out which languages are translatable and which translation "
"languages are available."
),
)
parser.add_argument(
"--webshare-proxy-username",
default=None,
type=str,
help='Specify your Webshare "Proxy Username" found at https://dashboard.webshare.io/proxy/settings',
)
parser.add_argument(
"--webshare-proxy-password",
default=None,
type=str,
help='Specify your Webshare "Proxy Password" found at https://dashboard.webshare.io/proxy/settings',
)
parser.add_argument(
"--http-proxy",
default="",
metavar="URL",
help="Use the specified HTTP proxy.",
)
parser.add_argument(
"--https-proxy",
default="",
metavar="URL",
help="Use the specified HTTPS proxy.",
)
# Cookie auth has been temporarily disabled, as it is not working properly with
# YouTube's most recent changes.
# parser.add_argument(
# "--cookies",
# default=None,
# help="The cookie file that will be used for authorization with youtube.",
# )
return self._sanitize_video_ids(parser.parse_args(self._args))
def _sanitize_video_ids(self, args):
args.video_ids = [video_id.replace("\\", "") for video_id in args.video_ids]
return args
================================================
FILE: youtube_transcript_api/_errors.py
================================================
from pathlib import Path
from typing import Iterable, Optional, List
from requests import HTTPError
from ._settings import WATCH_URL
from .proxies import ProxyConfig, GenericProxyConfig, WebshareProxyConfig
class YouTubeTranscriptApiException(Exception):
pass
class CookieError(YouTubeTranscriptApiException):
pass
class CookiePathInvalid(CookieError):
def __init__(
self, cookie_path: Path
): # pragma: no cover until cookie authentication is re-implemented
super().__init__(f"Can't load the provided cookie file: {cookie_path}")
class CookieInvalid(CookieError):
def __init__(
self, cookie_path: Path
): # pragma: no cover until cookie authentication is re-implemented
super().__init__(
f"The cookies provided are not valid (may have expired): {cookie_path}"
)
class CouldNotRetrieveTranscript(YouTubeTranscriptApiException):
"""
Raised if a transcript could not be retrieved.
"""
ERROR_MESSAGE = "\nCould not retrieve a transcript for the video {video_url}!"
CAUSE_MESSAGE_INTRO = " This is most likely caused by:\n\n{cause}"
CAUSE_MESSAGE = ""
GITHUB_REFERRAL = (
"\n\nIf you are sure that the described cause is not responsible for this error "
"and that a transcript should be retrievable, please create an issue at "
"https://github.com/jdepoix/youtube-transcript-api/issues. "
"Please add which version of youtube_transcript_api you are using "
"and provide the information needed to replicate the error. "
"Also make sure that there are no open issues which already describe your problem!"
)
def __init__(self, video_id: str):
self.video_id = video_id
super().__init__()
def _build_error_message(self) -> str:
error_message = self.ERROR_MESSAGE.format(
video_url=WATCH_URL.format(video_id=self.video_id)
)
cause = self.cause
if cause:
error_message += (
self.CAUSE_MESSAGE_INTRO.format(cause=cause) + self.GITHUB_REFERRAL
)
return error_message
@property
def cause(self) -> str:
return self.CAUSE_MESSAGE
def __str__(self) -> str:
return self._build_error_message()
class YouTubeDataUnparsable(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = (
"The data required to fetch the transcript is not parsable. This should "
"not happen, please open an issue (make sure to include the video ID)!"
)
class YouTubeRequestFailed(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "Request to YouTube failed: {reason}"
def __init__(self, video_id: str, http_error: HTTPError):
self.reason = str(http_error)
super().__init__(video_id)
@property
def cause(self) -> str:
return self.CAUSE_MESSAGE.format(
reason=self.reason,
)
class VideoUnplayable(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "The video is unplayable for the following reason: {reason}"
SUBREASON_MESSAGE = "\n\nAdditional Details:\n{sub_reasons}"
def __init__(self, video_id: str, reason: Optional[str], sub_reasons: List[str]):
self.reason = reason
self.sub_reasons = sub_reasons
super().__init__(video_id)
@property
def cause(self):
reason = "No reason specified!" if self.reason is None else self.reason
if self.sub_reasons:
sub_reasons = "\n".join(
f" - {sub_reason}" for sub_reason in self.sub_reasons
)
reason = f"{reason}{self.SUBREASON_MESSAGE.format(sub_reasons=sub_reasons)}"
return self.CAUSE_MESSAGE.format(
reason=reason,
)
class VideoUnavailable(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "The video is no longer available"
class InvalidVideoId(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = (
"You provided an invalid video id. Make sure you are using the video id and NOT the url!\n\n"
'Do NOT run: `YouTubeTranscriptApi().fetch("https://www.youtube.com/watch?v=1234")`\n'
'Instead run: `YouTubeTranscriptApi().fetch("1234")`'
)
class RequestBlocked(CouldNotRetrieveTranscript):
BASE_CAUSE_MESSAGE = (
"YouTube is blocking requests from your IP. This usually is due to one of the "
"following reasons:\n"
"- You have done too many requests and your IP has been blocked by YouTube\n"
"- You are doing requests from an IP belonging to a cloud provider (like AWS, "
"Google Cloud Platform, Azure, etc.). Unfortunately, most IPs from cloud "
"providers are blocked by YouTube.\n\n"
)
CAUSE_MESSAGE = (
f"{BASE_CAUSE_MESSAGE}"
"There are two things you can do to work around this:\n"
'1. Use proxies to hide your IP address, as explained in the "Working around '
'IP bans" section of the README '
"(https://github.com/jdepoix/youtube-transcript-api"
"?tab=readme-ov-file"
"#working-around-ip-bans-requestblocked-or-ipblocked-exception).\n"
"2. (NOT RECOMMENDED) If you authenticate your requests using cookies, you "
"will be able to continue doing requests for a while. However, YouTube will "
"eventually permanently ban the account that you have used to authenticate "
"with! So only do this if you don't mind your account being banned!"
)
WITH_GENERIC_PROXY_CAUSE_MESSAGE = (
"YouTube is blocking your requests, despite you using proxies. Keep in mind "
"that a proxy is just a way to hide your real IP behind the IP of that proxy, "
"but there is no guarantee that the IP of that proxy won't be blocked as "
"well.\n\n"
"The only truly reliable way to prevent IP blocks is rotating through a large "
"pool of residential IPs, by using a provider like Webshare "
"(https://www.webshare.io/?referral_code=w0xno53eb50g), which provides you "
"with a pool of >30M residential IPs (make sure to purchase "
'"Residential" proxies, NOT "Proxy Server" or "Static Residential"!).\n\n'
"You will find more information on how to easily integrate Webshare here: "
"https://github.com/jdepoix/youtube-transcript-api"
"?tab=readme-ov-file#using-webshare"
)
WITH_WEBSHARE_PROXY_CAUSE_MESSAGE = (
"YouTube is blocking your requests, despite you using Webshare proxies. "
'Please make sure that you have purchased "Residential" proxies and '
'NOT "Proxy Server" or "Static Residential", as those won\'t work as '
'reliably! The free tier also uses "Proxy Server" and will NOT work!\n\n'
'The only reliable option is using "Residential" proxies (not "Static '
'Residential"), as this allows you to rotate through a pool of over 30M IPs, '
"which means you will always find an IP that hasn't been blocked by YouTube "
"yet!\n\n"
"You can support the development of this open source project by making your "
"Webshare purchases through this affiliate link: "
"https://www.webshare.io/?referral_code=w0xno53eb50g \n\n"
"Thank you for your support! <3"
)
def __init__(self, video_id: str):
self._proxy_config = None
super().__init__(video_id)
def with_proxy_config(
self, proxy_config: Optional[ProxyConfig]
) -> "RequestBlocked":
self._proxy_config = proxy_config
return self
@property
def cause(self) -> str:
if isinstance(self._proxy_config, WebshareProxyConfig):
return self.WITH_WEBSHARE_PROXY_CAUSE_MESSAGE
if isinstance(self._proxy_config, GenericProxyConfig):
return self.WITH_GENERIC_PROXY_CAUSE_MESSAGE
return super().cause
class IpBlocked(RequestBlocked):
CAUSE_MESSAGE = (
f"{RequestBlocked.BASE_CAUSE_MESSAGE}"
'Ways to work around this are explained in the "Working around IP '
'bans" section of the README (https://github.com/jdepoix/youtube-transcript-api'
"?tab=readme-ov-file"
"#working-around-ip-bans-requestblocked-or-ipblocked-exception).\n"
)
class TranscriptsDisabled(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "Subtitles are disabled for this video"
class AgeRestricted(CouldNotRetrieveTranscript):
# CAUSE_MESSAGE = (
# "This video is age-restricted. Therefore, you will have to authenticate to be "
# "able to retrieve transcripts for it. You will have to provide a cookie to "
# 'authenticate yourself, as explained in the "Cookie Authentication" section of '
# "the README (https://github.com/jdepoix/youtube-transcript-api"
# "?tab=readme-ov-file#cookie-authentication)"
# )
CAUSE_MESSAGE = (
"This video is age-restricted. Therefore, you are unable to retrieve "
"transcripts for it without authenticating yourself.\n\n"
"Unfortunately, Cookie Authentication is temporarily unsupported in "
"youtube-transcript-api, as recent changes in YouTube's API broke the previous "
"implementation. I will do my best to re-implement it as soon as possible."
)
class NotTranslatable(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "The requested language is not translatable"
class TranslationLanguageNotAvailable(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "The requested translation language is not available"
class FailedToCreateConsentCookie(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = "Failed to automatically give consent to saving cookies"
class NoTranscriptFound(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = (
"No transcripts were found for any of the requested language codes: {requested_language_codes}\n\n"
"{transcript_data}"
)
def __init__(
self,
video_id: str,
requested_language_codes: Iterable[str],
transcript_data: "TranscriptList", # noqa: F821
):
self._requested_language_codes = requested_language_codes
self._transcript_data = transcript_data
super().__init__(video_id)
@property
def cause(self) -> str:
return self.CAUSE_MESSAGE.format(
requested_language_codes=self._requested_language_codes,
transcript_data=str(self._transcript_data),
)
class PoTokenRequired(CouldNotRetrieveTranscript):
CAUSE_MESSAGE = (
"The requested video cannot be retrieved without a PO Token. If this happens, "
"please open a GitHub issue!"
)
================================================
FILE: youtube_transcript_api/_settings.py
================================================
WATCH_URL = "https://www.youtube.com/watch?v={video_id}"
INNERTUBE_API_URL = "https://www.youtube.com/youtubei/v1/player?key={api_key}"
INNERTUBE_CONTEXT = {"client": {"clientName": "ANDROID", "clientVersion": "20.10.38"}}
================================================
FILE: youtube_transcript_api/_transcripts.py
================================================
from dataclasses import dataclass, asdict
from enum import Enum
from itertools import chain
from html import unescape
from typing import List, Dict, Iterator, Iterable, Pattern, Optional
from defusedxml import ElementTree
import re
from requests import HTTPError, Session, Response
from .proxies import ProxyConfig
from ._settings import WATCH_URL, INNERTUBE_CONTEXT, INNERTUBE_API_URL
from ._errors import (
VideoUnavailable,
YouTubeRequestFailed,
NoTranscriptFound,
TranscriptsDisabled,
NotTranslatable,
TranslationLanguageNotAvailable,
FailedToCreateConsentCookie,
InvalidVideoId,
IpBlocked,
RequestBlocked,
AgeRestricted,
VideoUnplayable,
YouTubeDataUnparsable,
PoTokenRequired,
)
@dataclass
class FetchedTranscriptSnippet:
text: str
start: float
"""
The timestamp at which this transcript snippet appears on screen in seconds.
"""
duration: float
"""
The duration of how long the snippet in seconds. Be aware that this is not the
duration of the transcribed speech, but how long the snippet stays on screen.
Therefore, there can be overlaps between snippets!
"""
@dataclass
class FetchedTranscript:
"""
Represents a fetched transcript. This object is iterable, which allows you to
iterate over the transcript snippets.
"""
snippets: List[FetchedTranscriptSnippet]
video_id: str
language: str
language_code: str
is_generated: bool
def __iter__(self) -> Iterator[FetchedTranscriptSnippet]:
return iter(self.snippets)
def __getitem__(self, index) -> FetchedTranscriptSnippet:
return self.snippets[index]
def __len__(self) -> int:
return len(self.snippets)
def to_raw_data(self) -> List[Dict]:
return [asdict(snippet) for snippet in self]
@dataclass
class _TranslationLanguage:
language: str
language_code: str
class _PlayabilityStatus(str, Enum):
OK = "OK"
ERROR = "ERROR"
LOGIN_REQUIRED = "LOGIN_REQUIRED"
class _PlayabilityFailedReason(str, Enum):
BOT_DETECTED = "Sign in to confirm you’re not a bot"
AGE_RESTRICTED = "This video may be inappropriate for some users."
VIDEO_UNAVAILABLE = "This video is unavailable"
def _raise_http_errors(response: Response, video_id: str) -> Response:
try:
if response.status_code == 429:
raise IpBlocked(video_id)
response.raise_for_status()
return response
except HTTPError as error:
raise YouTubeRequestFailed(video_id, error)
class Transcript:
def __init__(
self,
http_client: Session,
video_id: str,
url: str,
language: str,
language_code: str,
is_generated: bool,
translation_languages: List[_TranslationLanguage],
):
"""
You probably don't want to initialize this directly. Usually you'll access Transcript objects using a
TranscriptList.
"""
self._http_client = http_client
self.video_id = video_id
self._url = url
self.language = language
self.language_code = language_code
self.is_generated = is_generated
self.translation_languages = translation_languages
self._translation_languages_dict = {
translation_language.language_code: translation_language.language
for translation_language in translation_languages
}
def fetch(self, preserve_formatting: bool = False) -> FetchedTranscript:
"""
Loads the actual transcript data.
:param preserve_formatting: whether to keep select HTML text formatting
"""
if "&exp=xpe" in self._url:
raise PoTokenRequired(self.video_id)
response = self._http_client.get(self._url)
snippets = _TranscriptParser(preserve_formatting=preserve_formatting).parse(
_raise_http_errors(response, self.video_id).text,
)
return FetchedTranscript(
snippets=snippets,
video_id=self.video_id,
language=self.language,
language_code=self.language_code,
is_generated=self.is_generated,
)
def __str__(self) -> str:
return '{language_code} ("{language}"){translation_description}'.format(
language=self.language,
language_code=self.language_code,
translation_description="[TRANSLATABLE]" if self.is_translatable else "",
)
@property
def is_translatable(self) -> bool:
return len(self.translation_languages) > 0
def translate(self, language_code: str) -> "Transcript":
if not self.is_translatable:
raise NotTranslatable(self.video_id)
if language_code not in self._translation_languages_dict:
raise TranslationLanguageNotAvailable(self.video_id)
return Transcript(
self._http_client,
self.video_id,
"{url}&tlang={language_code}".format(
url=self._url, language_code=language_code
),
self._translation_languages_dict[language_code],
language_code,
True,
[],
)
class TranscriptList:
"""
This object represents a list of transcripts. It can be iterated over to list all transcripts which are available
for a given YouTube video. Also, it provides functionality to search for a transcript in a given language.
"""
def __init__(
self,
video_id: str,
manually_created_transcripts: Dict[str, Transcript],
generated_transcripts: Dict[str, Transcript],
translation_languages: List[_TranslationLanguage],
):
"""
The constructor is only for internal use. Use the static build method instead.
:param video_id: the id of the video this TranscriptList is for
:param manually_created_transcripts: dict mapping language codes to the manually created transcripts
:param generated_transcripts: dict mapping language codes to the generated transcripts
:param translation_languages: list of languages which can be used for translatable languages
"""
self.video_id = video_id
self._manually_created_transcripts = manually_created_transcripts
self._generated_transcripts = generated_transcripts
self._translation_languages = translation_languages
@staticmethod
def build(
http_client: Session, video_id: str, captions_json: Dict
) -> "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
:return: the created TranscriptList
"""
translation_languages = [
_TranslationLanguage(
language=translation_language["languageName"]["runs"][0]["text"],
language_code=translation_language["languageCode"],
)
for translation_language in captions_json.get("translationLanguages", [])
]
manually_created_transcripts = {}
generated_transcripts = {}
for caption in captions_json["captionTracks"]:
if caption.get("kind", "") == "asr":
transcript_dict = generated_transcripts
else:
transcript_dict = manually_created_transcripts
transcript_dict[caption["languageCode"]] = Transcript(
http_client,
video_id,
caption["baseUrl"].replace("&fmt=srv3", ""),
caption["name"]["runs"][0]["text"],
caption["languageCode"],
caption.get("kind", "") == "asr",
translation_languages if caption.get("isTranslatable", False) else [],
)
return TranscriptList(
video_id,
manually_created_transcripts,
generated_transcripts,
translation_languages,
)
def __iter__(self) -> Iterator[Transcript]:
return chain(
self._manually_created_transcripts.values(),
self._generated_transcripts.values(),
)
def find_transcript(self, language_codes: Iterable[str]) -> Transcript:
"""
Finds a transcript for a given language code. Manually created transcripts are returned first and only if none
are found, generated transcripts are used. If you only want generated transcripts use
`find_manually_created_transcript` instead.
:param language_codes: A list of language codes in a descending priority. For example, if this is set to
['de', 'en'] it will first try to fetch the german transcript (de) and then fetch the english transcript (en) if
it fails to do so.
:return: the found Transcript
"""
return self._find_transcript(
language_codes,
[self._manually_created_transcripts, self._generated_transcripts],
)
def find_generated_transcript(self, language_codes: Iterable[str]) -> Transcript:
"""
Finds an automatically generated transcript for a given language code.
:param language_codes: A list of language codes in a descending priority. For example, if this is set to
['de', 'en'] it will first try to fetch the german transcript (de) and then fetch the english transcript (en) if
it fails to do so.
:return: the found Transcript
"""
return self._find_transcript(language_codes, [self._generated_transcripts])
def find_manually_created_transcript(
self, language_codes: Iterable[str]
) -> Transcript:
"""
Finds a manually created transcript for a given language code.
:param language_codes: A list of language codes in a descending priority. For example, if this is set to
['de', 'en'] it will first try to fetch the german transcript (de) and then fetch the english transcript (en) if
it fails to do so.
:return: the found Transcript
"""
return self._find_transcript(
language_codes, [self._manually_created_transcripts]
)
def _find_transcript(
self,
language_codes: Iterable[str],
transcript_dicts: List[Dict[str, Transcript]],
) -> Transcript:
for language_code in language_codes:
for transcript_dict in transcript_dicts:
if language_code in transcript_dict:
return transcript_dict[language_code]
raise NoTranscriptFound(self.video_id, language_codes, self)
def __str__(self) -> str:
return (
"For this video ({video_id}) transcripts are available in the following languages:\n\n"
"(MANUALLY CREATED)\n"
"{available_manually_created_transcript_languages}\n\n"
"(GENERATED)\n"
"{available_generated_transcripts}\n\n"
"(TRANSLATION LANGUAGES)\n"
"{available_translation_languages}"
).format(
video_id=self.video_id,
available_manually_created_transcript_languages=self._get_language_description(
str(transcript)
for transcript in self._manually_created_transcripts.values()
),
available_generated_transcripts=self._get_language_description(
str(transcript) for transcript in self._generated_transcripts.values()
),
available_translation_languages=self._get_language_description(
'{language_code} ("{language}")'.format(
language=translation_language.language,
language_code=translation_language.language_code,
)
for translation_language in self._translation_languages
),
)
def _get_language_description(self, transcript_strings: Iterable[str]) -> str:
description = "\n".join(
" - {transcript}".format(transcript=transcript)
for transcript in transcript_strings
)
return description if description else "None"
class TranscriptListFetcher:
def __init__(self, http_client: Session, proxy_config: Optional[ProxyConfig]):
self._http_client = http_client
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),
)
def _fetch_captions_json(self, video_id: str, try_number: int = 0) -> Dict:
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)
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)
raise exception.with_proxy_config(self._proxy_config)
def _extract_innertube_api_key(self, html: str, video_id: str) -> str:
pattern = r'"INNERTUBE_API_KEY":\s*"([a-zA-Z0-9_-]+)"'
match = re.search(pattern, html)
if match and len(match.groups()) == 1:
return match.group(1)
if 'class="g-recaptcha"' in html:
raise IpBlocked(video_id)
raise YouTubeDataUnparsable(video_id) # pragma: no cover
def _extract_captions_json(self, innertube_data: Dict, video_id: str) -> Dict:
self._assert_playability(innertube_data.get("playabilityStatus"), video_id)
captions_json = innertube_data.get("captions", {}).get(
"playerCaptionsTracklistRenderer"
)
if captions_json is None or "captionTracks" not in captions_json:
raise TranscriptsDisabled(video_id)
return captions_json
def _assert_playability(self, playability_status_data: Dict, video_id: str) -> None:
playability_status = playability_status_data.get("status")
if (
playability_status != _PlayabilityStatus.OK.value
and playability_status is not None
):
reason = playability_status_data.get("reason")
if playability_status == _PlayabilityStatus.LOGIN_REQUIRED.value:
if reason == _PlayabilityFailedReason.BOT_DETECTED.value:
raise RequestBlocked(video_id)
if reason == _PlayabilityFailedReason.AGE_RESTRICTED.value:
raise AgeRestricted(video_id)
if (
playability_status == _PlayabilityStatus.ERROR.value
and reason == _PlayabilityFailedReason.VIDEO_UNAVAILABLE.value
):
if video_id.startswith("http://") or video_id.startswith("https://"):
raise InvalidVideoId(video_id)
raise VideoUnavailable(video_id)
subreasons = (
playability_status_data.get("errorScreen", {})
.get("playerErrorMessageRenderer", {})
.get("subreason", {})
.get("runs", [])
)
raise VideoUnplayable(
video_id, reason, [run.get("text", "") for run in subreasons]
)
def _create_consent_cookie(self, html: str, video_id: str) -> None:
match = re.search('name="v" value="(.*?)"', html)
if match is None:
raise FailedToCreateConsentCookie(video_id)
self._http_client.cookies.set(
"CONSENT", "YES+" + match.group(1), domain=".youtube.com"
)
def _fetch_video_html(self, video_id: str) -> str:
html = self._fetch_html(video_id)
if 'action="https://consent.youtube.com/s"' in html:
self._create_consent_cookie(html, video_id)
html = self._fetch_html(video_id)
if 'action="https://consent.youtube.com/s"' in html:
raise FailedToCreateConsentCookie(video_id)
return html
def _fetch_html(self, video_id: str) -> str:
response = self._http_client.get(WATCH_URL.format(video_id=video_id))
return unescape(_raise_http_errors(response, video_id).text)
def _fetch_innertube_data(self, video_id: str, api_key: str) -> Dict:
response = self._http_client.post(
INNERTUBE_API_URL.format(api_key=api_key),
json={
"context": INNERTUBE_CONTEXT,
"videoId": video_id,
},
)
data = _raise_http_errors(response, video_id).json()
return data
class _TranscriptParser:
_FORMATTING_TAGS = [
"strong", # important
"em", # emphasized
"b", # bold
"i", # italic
"mark", # marked
"small", # smaller
"del", # deleted
"ins", # inserted
"sub", # subscript
"sup", # superscript
]
def __init__(self, preserve_formatting: bool = False):
self._html_regex = self._get_html_regex(preserve_formatting)
def _get_html_regex(self, preserve_formatting: bool) -> Pattern[str]:
if preserve_formatting:
formats_regex = "|".join(self._FORMATTING_TAGS)
formats_regex = r"<\/?(?!\/?(" + formats_regex + r")\b).*?\b>"
html_regex = re.compile(formats_regex, re.IGNORECASE)
else:
html_regex = re.compile(r"<[^>]*>", re.IGNORECASE)
return html_regex
def parse(self, raw_data: str) -> List[FetchedTranscriptSnippet]:
return [
FetchedTranscriptSnippet(
text=re.sub(self._html_regex, "", unescape(xml_element.text)),
start=float(xml_element.attrib["start"]),
duration=float(xml_element.attrib.get("dur", "0.0")),
)
for xml_element in ElementTree.fromstring(raw_data)
if xml_element.text is not None
]
================================================
FILE: youtube_transcript_api/formatters.py
================================================
import json
import pprint
from typing import List, Iterable
from ._transcripts import FetchedTranscript, FetchedTranscriptSnippet
class Formatter:
"""Formatter should be used as an abstract base class.
Formatter classes should inherit from this class and implement
their own .format() method which should return a string. A
transcript is represented by a List of Dictionary items.
"""
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
raise NotImplementedError(
"A subclass of Formatter must implement "
"their own .format_transcript() method."
)
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs):
raise NotImplementedError(
"A subclass of Formatter must implement "
"their own .format_transcripts() method."
)
class PrettyPrintFormatter(Formatter):
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
"""Pretty prints a transcript.
:param transcript:
:return: A pretty printed string representation of the transcript.
"""
return pprint.pformat(transcript.to_raw_data(), **kwargs)
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str:
"""Converts a list of transcripts into a JSON string.
:param transcripts:
:return: A JSON string representation of the transcript.
"""
return pprint.pformat(
[transcript.to_raw_data() for transcript in transcripts], **kwargs
)
class JSONFormatter(Formatter):
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
"""Converts a transcript into a JSON string.
:param transcript:
:return: A JSON string representation of the transcript.
"""
return json.dumps(transcript.to_raw_data(), **kwargs)
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str:
"""Converts a list of transcripts into a JSON string.
:param transcripts:
:return: A JSON string representation of the transcript.
"""
return json.dumps(
[transcript.to_raw_data() for transcript in transcripts], **kwargs
)
class TextFormatter(Formatter):
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
"""Converts a transcript into plain text with no timestamps.
:param transcript:
:return: all transcript text lines separated by newline breaks.
"""
return "\n".join(line.text for line in transcript)
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str:
"""Converts a list of transcripts into plain text with no timestamps.
:param transcripts:
:return: all transcript text lines separated by newline breaks.
"""
return "\n\n\n".join(
[self.format_transcript(transcript, **kwargs) for transcript in transcripts]
)
class _TextBasedFormatter(TextFormatter):
def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int) -> str:
raise NotImplementedError(
"A subclass of _TextBasedFormatter must implement "
"their own .format_timestamp() method."
)
def _format_transcript_header(self, lines: Iterable[str]) -> str:
raise NotImplementedError(
"A subclass of _TextBasedFormatter must implement "
"their own _format_transcript_header method."
)
def _format_transcript_helper(
self, i: int, time_text: str, snippet: FetchedTranscriptSnippet
) -> str:
raise NotImplementedError(
"A subclass of _TextBasedFormatter must implement "
"their own _format_transcript_helper method."
)
def _seconds_to_timestamp(self, time: float) -> str:
"""Helper that converts `time` into a transcript cue timestamp.
:reference: https://www.w3.org/TR/webvtt1/#webvtt-timestamp
:param time: a float representing time in seconds.
:type time: float
:return: a string formatted as a cue timestamp, 'HH:MM:SS.MS'
:example:
>>> self._seconds_to_timestamp(6.93)
'00:00:06.930'
"""
time = float(time)
hours_float, remainder = divmod(time, 3600)
mins_float, secs_float = divmod(remainder, 60)
hours, mins, secs = int(hours_float), int(mins_float), int(secs_float)
ms = int(round((time - int(time)) * 1000, 2))
return self._format_timestamp(hours, mins, secs, ms)
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
"""A basic implementation of WEBVTT/SRT formatting.
:param transcript:
:reference:
https://www.w3.org/TR/webvtt1/#introduction-caption
https://www.3playmedia.com/blog/create-srt-file/
"""
lines = []
for i, line in enumerate(transcript):
end = line.start + line.duration
time_text = "{} --> {}".format(
self._seconds_to_timestamp(line.start),
self._seconds_to_timestamp(
transcript[i + 1].start
if i < len(transcript) - 1 and transcript[i + 1].start < end
else end
),
)
lines.append(self._format_transcript_helper(i, time_text, line))
return self._format_transcript_header(lines)
class SRTFormatter(_TextBasedFormatter):
def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int) -> str:
return "{:02d}:{:02d}:{:02d},{:03d}".format(hours, mins, secs, ms)
def _format_transcript_header(self, lines: Iterable[str]) -> str:
return "\n\n".join(lines) + "\n"
def _format_transcript_helper(
self, i: int, time_text: str, snippet: FetchedTranscriptSnippet
) -> str:
return "{}\n{}\n{}".format(i + 1, time_text, snippet.text)
class WebVTTFormatter(_TextBasedFormatter):
def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int) -> str:
return "{:02d}:{:02d}:{:02d}.{:03d}".format(hours, mins, secs, ms)
def _format_transcript_header(self, lines: Iterable[str]) -> str:
return "WEBVTT\n\n" + "\n\n".join(lines) + "\n"
def _format_transcript_helper(
self, i: int, time_text: str, snippet: FetchedTranscriptSnippet
) -> str:
return "{}\n{}".format(time_text, snippet.text)
class FormatterLoader:
TYPES = {
"json": JSONFormatter,
"pretty": PrettyPrintFormatter,
"text": TextFormatter,
"webvtt": WebVTTFormatter,
"srt": SRTFormatter,
}
class UnknownFormatterType(Exception):
def __init__(self, formatter_type: str):
super().__init__(
"The format '{formatter_type}' is not supported. "
"Choose one of the following formats: {supported_formatter_types}".format(
formatter_type=formatter_type,
supported_formatter_types=", ".join(FormatterLoader.TYPES.keys()),
)
)
def load(self, formatter_type: str = "pretty") -> Formatter:
"""
Loads the Formatter for the given formatter type.
:param formatter_type:
:return: Formatter object
"""
if formatter_type not in FormatterLoader.TYPES.keys():
raise FormatterLoader.UnknownFormatterType(formatter_type)
return FormatterLoader.TYPES[formatter_type]()
================================================
FILE: youtube_transcript_api/proxies.py
================================================
from abc import ABC, abstractmethod
from typing import TypedDict, Optional, List
class InvalidProxyConfig(Exception):
pass
class RequestsProxyConfigDict(TypedDict):
"""
This type represents the Dict that is used by the requests library to configure
the proxies used. More information on this can be found in the official requests
documentation: https://requests.readthedocs.io/en/latest/user/advanced/#proxies
"""
http: str
https: str
class ProxyConfig(ABC):
"""
The base class for all proxy configs. Anything can be a proxy config, as longs as
it can be turned into a `RequestsProxyConfigDict` by calling `to_requests_dict`.
"""
@abstractmethod
def to_requests_dict(self) -> RequestsProxyConfigDict:
"""
Turns this proxy config into the Dict that is expected by the requests library.
More information on this can be found in the official requests documentation:
https://requests.readthedocs.io/en/latest/user/advanced/#proxies
"""
pass
@property
def prevent_keeping_connections_alive(self) -> bool:
"""
If you are using rotating proxies, it can be useful to prevent the HTTP
client from keeping TCP connections alive, as your IP won't be rotated on
every request, if your connection stays open.
"""
return False
@property
def retries_when_blocked(self) -> int:
"""
Defines how many times we should retry if a request is blocked. When using
rotating residential proxies with a large IP pool it can make sense to retry a
couple of times when a blocked IP is encountered, since a retry will trigger
an IP rotation and the next IP might not be blocked.
"""
return 0
class GenericProxyConfig(ProxyConfig):
"""
This proxy config can be used to set up any generic HTTP/HTTPS/SOCKS proxy. As it
the requests library is used under the hood, you can follow the requests
documentation to get more detailed information on how to set up proxies:
https://requests.readthedocs.io/en/latest/user/advanced/#proxies
If only an HTTP or an HTTPS proxy is provided, it will be used for both types of
connections. However, you will have to provide at least one of the two.
"""
def __init__(self, http_url: Optional[str] = None, https_url: Optional[str] = None):
"""
If only an HTTP or an HTTPS proxy is provided, it will be used for both types of
connections. However, you will have to provide at least one of the two.
:param http_url: the proxy URL used for HTTP requests. Defaults to `https_url`
if None.
:param https_url: the proxy URL used for HTTPS requests. Defaults to `http_url`
if None.
"""
if not http_url and not https_url:
raise InvalidProxyConfig(
"GenericProxyConfig requires you to define at least one of the two: "
"http or https"
)
self.http_url = http_url
self.https_url = https_url
def to_requests_dict(self) -> RequestsProxyConfigDict:
return {
"http": self.http_url or self.https_url,
"https": self.https_url or self.http_url,
}
class WebshareProxyConfig(GenericProxyConfig):
"""
Webshare is a provider offering rotating residential proxies, which is the
most reliable way to work around being blocked by YouTube.
If you don't have a Webshare account yet, you will have to create one
at https://www.webshare.io/?referral_code=w0xno53eb50g and purchase a "Residential"
proxy package that suits your workload, to be able to use this proxy config (make
sure NOT to purchase "Proxy Server" or "Static Residential"!).
Once you have created an account you only need the "Proxy Username" and
"Proxy Password" that you can find in your Webshare settings
at https://dashboard.webshare.io/proxy/settings to set up this config class, which
will take care of setting up your proxies as needed, by defaulting to rotating
proxies.
Note that referral links are used here and any purchases made through these links
will support this Open Source project, which is very much appreciated! :)
However, you can of course integrate your own proxy solution by using the
`GenericProxyConfig` class, if that's what you prefer.
"""
DEFAULT_DOMAIN_NAME = "p.webshare.io"
DEFAULT_PORT = 80
def __init__(
self,
proxy_username: str,
proxy_password: str,
filter_ip_locations: Optional[List[str]] = None,
retries_when_blocked: int = 10,
domain_name: str = DEFAULT_DOMAIN_NAME,
proxy_port: int = DEFAULT_PORT,
):
"""
Once you have created a Webshare account at
https://www.webshare.io/?referral_code=w0xno53eb50g and purchased a
"Residential" package (make sure NOT to purchase "Proxy Server" or
"Static Residential"!), this config class allows you to easily use it,
by defaulting to the most reliable proxy settings (rotating residential
proxies).
:param proxy_username: "Proxy Username" found at
https://dashboard.webshare.io/proxy/settings
:param proxy_password: "Proxy Password" found at
https://dashboard.webshare.io/proxy/settings
:param filter_ip_locations: If you want to limit the pool of IPs that you will
be rotating through to those located in specific countries, you can provide
a list of location codes here. By choosing locations that are close to the
machine that is running this code, you can reduce latency. Also, this can
be used to work around location-based restrictions.
You can find the full list of available locations (and how many IPs are
available in each location) at
https://www.webshare.io/features/proxy-locations?referral_code=w0xno53eb50g
:param retries_when_blocked: Define how many times we should retry if a request
is blocked. When using rotating residential proxies with a large IP pool it
makes sense to retry a couple of times when a blocked IP is encountered,
since a retry will trigger an IP rotation and the next IP might not be
blocked. Defaults to 10.
"""
self.proxy_username = proxy_username
self.proxy_password = proxy_password
self.domain_name = domain_name
self.proxy_port = proxy_port
self._filter_ip_locations = filter_ip_locations or []
self._retries_when_blocked = retries_when_blocked
@property
def url(self) -> str:
location_codes = "".join(
f"-{location_code.upper()}" for location_code in self._filter_ip_locations
)
username = self.proxy_username
suffix = "-rotate"
if username.endswith(suffix):
username = username[: -len(suffix)]
return (
f"http://{username}{location_codes}{suffix}:{self.proxy_password}"
f"@{self.domain_name}:{self.proxy_port}/"
)
@property
def http_url(self) -> str:
return self.url
@property
def https_url(self) -> str:
return self.url
@property
def prevent_keeping_connections_alive(self) -> bool:
return True
@property
def retries_when_blocked(self) -> int:
return self._retries_when_blocked
================================================
FILE: youtube_transcript_api/py.typed
================================================
================================================
FILE: youtube_transcript_api/test/__init__.py
================================================
================================================
FILE: youtube_transcript_api/test/assets/__init__.py
================================================
================================================
FILE: youtube_transcript_api/test/assets/transcript.xml.static
================================================
<?xml version="1.0" encoding="utf-8" ?>
<transcript>
<text start="0" dur="1.54">Hey, this is just a test</text>
<text start="1.54" dur="4.16">this is <i>not</i> the original transcript</text>
<text start="5" dur="0.5"></text>
<text start="5.7" dur="3.239">just something shorter, I made up for testing</text>
</transcript>
================================================
FILE: youtube_transcript_api/test/assets/youtube.html.static
================================================
<!DOCTYPE html><html style="font-size: 10px;font-family: Roboto, Arial, sans-serif;" lang="en" darker-dark-theme darker-dark-theme-deprecate system-icons typography typography-spacing><head><script id="bc-def" nonce="mWvYBNEsFc3nGnwaOCaRHg">'use strict';function a(b,c){try{Object.defineProperty(b,c,{writable:!1,configurable:!1})}catch(d){}}a(window,"fetch");a(window,"JSON");a(window.JSON,"stringify");a(window.JSON,"parse");a(window,"Array");a(Array.prototype,"push");a(Array.prototype,"forEach");try{const b=document.getElementById("bc-def");b&&b.remove()}catch(b){};
</script><script data-id="_gd" nonce="mWvYBNEsFc3nGnwaOCaRHg">window.WIZ_global_data = {"HiPsbb":0,"MUE6Ne":"youtube_web","MuJWjd":true,"UUFaWc":"%.@.null,1000,2]","cfb2h":"youtube.web-front-end-critical_20250609.10_p0","fPDxwd":[],"iCzhFc":false,"nQyAE":{},"oxN3nb":{"1":false,"0":false,"610401301":false,"899588437":false,"725719775":false,"513659523":false,"568333945":false,"1331761403":false,"651175828":false,"722764542":false,"748402145":false,"748402146":false,"103340015":false,"555019702":false},"u4g7r":"%.@.null,1,2]","vJQk6":false,"xnI9P":true,"xwAfE":true,"yFnxrf":2486};</script><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta http-equiv="origin-trial" content="ApvK67ociHgr2egd6c2ZjrfPuRs8BHcvSggogIOPQNH7GJ3cVlyJ1NOq/COCdj0+zxskqHt9HgLLETc8qqD+vwsAAABteyJvcmlnaW4iOiJodHRwczovL3lvdXR1YmUuY29tOjQ0MyIsImZlYXR1cmUiOiJQcml2YWN5U2FuZGJveEFkc0FQSXMiLCJleHBpcnkiOjE2OTUxNjc5OTksImlzU3ViZG9tYWluIjp0cnVlfQ=="/><script nonce="mWvYBNEsFc3nGnwaOCaRHg">var ytcfg={d:function(){return window.yt&&yt.config_||ytcfg.data_||(ytcfg.data_={})},get:function(k,o){return k in ytcfg.d()?ytcfg.d()[k]:o},set:function(){var a=arguments;if(a.length>1)ytcfg.d()[a[0]]=a[1];else{var k;for(k in a[0])ytcfg.d()[k]=a[0][k]}}};
window.ytcfg.set('EMERGENCY_BASE_URL', '\/error_204?t\x3djserror\x26level\x3dERROR\x26client.name\x3d1\x26client.version\x3d2.20250610.00.00');</script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">(function(){window.yterr=window.yterr||true;window.unhandledErrorMessages={};window.unhandledErrorCount=0;
window.onerror=function(msg,url,line,columnNumber,error){var err;if(error)err=error;else{err=new Error;err.stack="";err.message=msg;err.fileName=url;err.lineNumber=line;if(!isNaN(columnNumber))err["columnNumber"]=columnNumber}var message=String(err.message);if(!err.message||message in window.unhandledErrorMessages||window.unhandledErrorCount>=5)return;window.unhandledErrorCount+=1;window.unhandledErrorMessages[message]=true;var img=new Image;window.emergencyTimeoutImg=img;img.onload=img.onerror=function(){delete window.emergencyTimeoutImg};
var combinedLineAndColumn=err.lineNumber;if(!isNaN(err["columnNumber"]))combinedLineAndColumn=combinedLineAndColumn+(":"+err["columnNumber"]);var stack=err.stack||"";var values={"msg":message,"type":err.name,"client.params":"unhandled window error","file":err.fileName,"line":combinedLineAndColumn,"stack":stack.substr(0,500)};var thirdPartyScript=!err.fileName||err.fileName==="<anonymous>"||stack.indexOf("extension://")>=0;var replaced=stack.replace(/https:\/\/www.youtube.com\//g,"");if(replaced.match(/https?:\/\/[^/]+\//))thirdPartyScript=
true;else if(stack.indexOf("trapProp")>=0&&stack.indexOf("trapChain")>=0)thirdPartyScript=true;else if(message.indexOf("redefine non-configurable")>=0)thirdPartyScript=true;var baseUrl=window["ytcfg"].get("EMERGENCY_BASE_URL","https://www.youtube.com/error_204?t=jserror&level=ERROR");var unsupported=message.indexOf("window.customElements is undefined")>=0;if(thirdPartyScript||unsupported)baseUrl=baseUrl.replace("level=ERROR","level=WARNING");var parts=[baseUrl];var key;for(key in values){var value=
values[key];if(value)parts.push(key+"="+encodeURIComponent(value))}img.src=parts.join("&")};
(function(){function _getExtendedNativePrototype(tag){var p=this._nativePrototypes[tag];if(!p){p=Object.create(this.getNativePrototype(tag));var p$=Object.getOwnPropertyNames(window["Polymer"].Base);var i=0;var n=void 0;for(;i<p$.length&&(n=p$[i]);i++)if(!window["Polymer"].BaseDescriptors[n])try{p[n]=window["Polymer"].Base[n]}catch(e){throw new Error("Error while copying property: "+n+". Tag is "+tag);}try{Object.defineProperties(p,window["Polymer"].BaseDescriptors)}catch(e){throw new Error("Polymer define property failed for "+
Object.keys(p));}this._nativePrototypes[tag]=p}return p}function handlePolymerError(msg){window.onerror(msg,window.location.href,0,0,new Error(Array.prototype.join.call(arguments,",")))}var origPolymer=window["Polymer"];var newPolymer=function(config){if(!origPolymer._ytIntercepted&&window["Polymer"].Base){origPolymer._ytIntercepted=true;window["Polymer"].Base._getExtendedNativePrototype=_getExtendedNativePrototype;window["Polymer"].Base._error=handlePolymerError;window["Polymer"].Base._warn=handlePolymerError}return origPolymer.apply(this,
arguments)};var origDescriptor=Object.getOwnPropertyDescriptor(window,"Polymer");Object.defineProperty(window,"Polymer",{set:function(p){if(origDescriptor&&origDescriptor.set&&origDescriptor.get){origDescriptor.set(p);origPolymer=origDescriptor.get()}else origPolymer=p;if(typeof origPolymer==="function")Object.defineProperty(window,"Polymer",{value:origPolymer,configurable:true,enumerable:true,writable:true})},get:function(){return typeof origPolymer==="function"?newPolymer:origPolymer},configurable:true,
enumerable:true})})();}).call(this);
</script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">window.Polymer=window.Polymer||{};window.Polymer.legacyOptimizations=true;window.Polymer.setPassiveTouchGestures=true;window.ShadyDOM={force:true,preferPerformance:true,noPatch:true};
window.polymerSkipLoadingFontRoboto = true;window.ShadyCSS = {disableRuntime: true};</script><link rel="shortcut icon" href="https://www.youtube.com/s/desktop/03ea48a4/img/logos/favicon.ico" type="image/x-icon"><link rel="icon" href="https://www.youtube.com/s/desktop/03ea48a4/img/logos/favicon_32x32.png" sizes="32x32"><link rel="icon" href="https://www.youtube.com/s/desktop/03ea48a4/img/logos/favicon_48x48.png" sizes="48x48"><link rel="icon" href="https://www.youtube.com/s/desktop/03ea48a4/img/logos/favicon_96x96.png" sizes="96x96"><link rel="icon" href="https://www.youtube.com/s/desktop/03ea48a4/img/logos/favicon_144x144.png" sizes="144x144"><script nonce="mWvYBNEsFc3nGnwaOCaRHg">if ('undefined' == typeof Symbol || 'undefined' == typeof Symbol.iterator) {delete Array.prototype.entries;}</script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">var ytcsi={gt:function(n){n=(n||"")+"data_";return ytcsi[n]||(ytcsi[n]={tick:{},info:{},gel:{preLoggedGelInfos:[]}})},now:window.performance&&window.performance.timing&&window.performance.now&&window.performance.timing.navigationStart?function(){return window.performance.timing.navigationStart+window.performance.now()}:function(){return(new Date).getTime()},tick:function(l,t,n){var ticks=ytcsi.gt(n).tick;var v=t||ytcsi.now();if(ticks[l]){ticks["_"+l]=ticks["_"+l]||[ticks[l]];ticks["_"+l].push(v)}ticks[l]=
v},info:function(k,v,n){ytcsi.gt(n).info[k]=v},infoGel:function(p,n){ytcsi.gt(n).gel.preLoggedGelInfos.push(p)},setStart:function(t,n){ytcsi.tick("_start",t,n)}};
(function(w,d){function isGecko(){if(!w.navigator)return false;try{if(w.navigator.userAgentData&&w.navigator.userAgentData.brands&&w.navigator.userAgentData.brands.length){var brands=w.navigator.userAgentData.brands;var i=0;for(;i<brands.length;i++)if(brands[i]&&brands[i].brand==="Firefox")return true;return false}}catch(e){setTimeout(function(){throw e;})}if(!w.navigator.userAgent)return false;var ua=w.navigator.userAgent;return ua.indexOf("Gecko")>0&&ua.toLowerCase().indexOf("webkit")<0&&ua.indexOf("Edge")<
0&&ua.indexOf("Trident")<0&&ua.indexOf("MSIE")<0}ytcsi.setStart(w.performance?w.performance.timing.responseStart:null);var isPrerender=(d.visibilityState||d.webkitVisibilityState)=="prerender";var vName=!d.visibilityState&&d.webkitVisibilityState?"webkitvisibilitychange":"visibilitychange";if(isPrerender){var startTick=function(){ytcsi.setStart();d.removeEventListener(vName,startTick)};d.addEventListener(vName,startTick,false)}if(d.addEventListener)d.addEventListener(vName,function(){ytcsi.tick("vc")},
false);if(isGecko()){var isHidden=(d.visibilityState||d.webkitVisibilityState)=="hidden";if(isHidden)ytcsi.tick("vc")}var slt=function(el,t){setTimeout(function(){var n=ytcsi.now();el.loadTime=n;if(el.slt)el.slt()},t)};w.__ytRIL=function(el){if(!el.getAttribute("data-thumb"))if(w.requestAnimationFrame)w.requestAnimationFrame(function(){slt(el,0)});else slt(el,16)}})(window,document);
</script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">(function() {var img = new Image().src = "https://i.ytimg.com/generate_204";})();</script><script src="https://www.youtube.com/s/desktop/03ea48a4/jsbin/web-animations-next-lite.min.vflset/web-animations-next-lite.min.js" nonce="mWvYBNEsFc3nGnwaOCaRHg"></script><script src="https://www.youtube.com/s/desktop/03ea48a4/jsbin/webcomponents-all-noPatch.vflset/webcomponents-all-noPatch.js" nonce="mWvYBNEsFc3nGnwaOCaRHg"></script><script src="https://www.youtube.com/s/desktop/03ea48a4/jsbin/fetch-polyfill.vflset/fetch-polyfill.js" nonce="mWvYBNEsFc3nGnwaOCaRHg"></script><script src="https://www.youtube.com/s/desktop/03ea48a4/jsbin/intersection-observer.min.vflset/intersection-observer.min.js" nonce="mWvYBNEsFc3nGnwaOCaRHg"></script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">if (window.ytcsi) {window.ytcsi.tick('lpcs', null, '');}</script><script nonce="mWvYBNEsFc3nGnwaOCaRHg">(function() {window.ytplayer={};
ytcfg.set({"CLIENT_CANARY_STATE":"none","DEVICE":"cbrand\u003drobot\u0026ceng\u003dUSER_DEFINED\u0026cmodel\u003dbot+or+crawler\u0026cplatform\u003dDESKTOP","DISABLE_YT_IMG_DELAY_LOADING":false,"ELEMENT_POOL_DEFAULT_CAP":75,"EVENT_ID":"xZ1JaISSF6qYsvQPoIS5oQg","EXPERIMENT_FLAGS":{"H5_enable_full_pacf_logging":true,"H5_use_async_logging":true,"ab_det_apb_b":true,"ab_det_apm":true,"ab_det_el_h":true,"ab_det_em_inj":true,"ab_det_pp_ov":true,"ab_l_sig_st":true,"ab_l_sig_st_e":true,"ab_sa_ef":true,"action_companion_center_align_description":true,"align_three_dot_menu_with_title_description":true,"allow_empty_path_in_url":true,"allow_skip_networkless":true,"att_web_record_metrics":true,"attmusi":true,"bg_st_hr":true,"browse_next_continuations_migration_playlist":true,"c3_watch_page_component":true,"cache_utc_offset_minutes_in_pref_cookie":true,"cancel_pending_navs":true,"check_user_lact_at_prompt_shown_time_on_web":true,"clean_up_manual_attribution_header":true,"clear_user_partitioned_ls":true,"client_respect_autoplay_switch_button_renderer":true,"cold_missing_history":true,"comments_simplebox_round_avatar":true,"composed_path_browser_fallback":true,"compress_gel":true,"config_age_report_killswitch":true,"cow_optimize_idom_compat":true,"csi_config_handling_infra":true,"csi_on_gel":true,"decorate_autoplay_renderer":true,"defer_menus":true,"defer_overlays":true,"defer_rendering_outside_visible_area":true,"deprecate_csi_has_info":true,"deprecate_pair_servlet_enabled":true,"desktop_add_to_playlist_renderer_dialog_popup":true,"desktop_animate_miniplayer":true,"desktop_delay_player_resizing":true,"desktop_enable_dmpanel_click_drag_scroll":true,"desktop_enable_dmpanel_scroll":true,"desktop_enable_dmpanel_wheel_scroll":true,"desktop_enable_new_video_metadata":true,"desktop_enable_scrollable_suggestions_panel":true,"desktop_enable_single_suggestion_highlight":true,"desktop_enable_suggestions_panel_padding":true,"desktop_enable_visual_suggest":true,"desktop_keyboard_capture_keydown_killswitch":true,"desktop_mix_use_sampled_color_for_bottom_bar":true,"desktop_mix_use_sampled_color_for_bottom_bar_search":true,"desktop_mix_use_sampled_color_for_bottom_bar_watch_next":true,"desktop_notification_set_title_bar":true,"desktop_shorts_persistent_panel_comments_default":true,"desktop_shorts_v2_anchored_panel":true,"desktop_shorts_v2_persistent_anchored_panel":true,"desktop_shorts_v2_persistent_anchored_panel_arrow":true,"desktop_shorts_volume_controls":true,"desktop_sitelinks_inline_descriptions":true,"desktop_sparkles_light_cta_button":true,"desktop_use_new_history_manager":true,"disable_cached_masthead_data":true,"disable_child_node_auto_formatted_strings":true,"disable_dependency_injection":true,"disable_enf_isd":true,"disable_features_for_supex":true,"disable_frosted_glass_on_shorts":true,"disable_legacy_desktop_remote_queue":true,"disable_log_to_visitor_layer":true,"disable_mention_encoded_channel_handle_in_post":true,"disable_pacf_logging_for_memory_limited_tv":true,"disable_pause_on_linked_comment_nav":true,"disable_super_chat_buy_button":true,"embeds_web_nwl_disable_nocookie":true,"empty_attributed_string_killswitch":true,"enable_ab_report_on_errorscreen":true,"enable_ab_rp_int":true,"enable_active_view_display_ad_renderer_web_home":true,"enable_ad_context_in_vss_pings":true,"enable_add_to_toast_action_command_resolver":true,"enable_ads_web_ep_buenos_aires_and_padding_fix":true,"enable_async_ab_enf":true,"enable_browser_cookie_status_monitoring":true,"enable_buenos_aires_typography":true,"enable_button_behavior_reuse":true,"enable_cairo_refresh_ringo2_web":true,"enable_chrome_related_websites_storage_access":true,"enable_client_creator_goal_ticker_bar_revamp":true,"enable_client_only_wiz_direct_reactions":true,"enable_client_sli_logging":true,"enable_client_streamz_web":true,"enable_client_ve_spec":true,"enable_cloud_save_error_popup_after_retry":true,"enable_command_handler_screen_manager":true,"enable_connect_icon_update_web":true,"enable_cow_info_csi":true,"enable_creator_details_shelf_m2":true,"enable_dai_sdf_h5_preroll":true,"enable_datasync_id_header_in_web_vss_pings":true,"enable_description_content_list_support":true,"enable_desktop_amsterdam_info_panels":true,"enable_desktop_fountain_reaction_item_data_consumption":true,"enable_desktop_search_bigger_thumbs":true,"enable_desktop_search_bigger_thumbs_square":true,"enable_dma_post_enforcement":true,"enable_docked_chat_messages":true,"enable_entity_store_from_dependency_injection":true,"enable_flow_logging_p4e":true,"enable_fully_reactive_badge_shape":true,"enable_fully_reactive_button_shape":true,"enable_fully_reactive_chip_shape":true,"enable_fully_reactive_chip_view_model":true,"enable_gel_log_commands":true,"enable_get_account_switcher_endpoint_on_webfe":true,"enable_handle_search_on_channel_switcher":true,"enable_handles_account_menu_switcher":true,"enable_handles_in_mention_suggest_posts":true,"enable_hlp_client_icon_pick":true,"enable_image_poll_post_creation":true,"enable_inline_shorts_on_wn":true,"enable_installment_wave2_view_agreement":true,"enable_interstitial_entity_check":true,"enable_is_extended_monitoring":true,"enable_is_mini_app_page_active_bugfix":true,"enable_lcr_emoji_fountain":true,"enable_logging_first_user_action_after_game_ready":true,"enable_lwe_web_mute":true,"enable_masthead_quartile_ping_fix":true,"enable_memberships_and_purchases":true,"enable_mentions_in_reposts":true,"enable_microformat_data":true,"enable_mini_app_iframe_loaded_logging":true,"enable_mixed_direction_formatted_strings":true,"enable_multi_image_post_creation":true,"enable_names_handles_account_switcher":true,"enable_native_bridge_view_saved_playables":true,"enable_network_request_logging_on_game_events":true,"enable_new_channel_creation_for_id4all":true,"enable_obtaining_ppn_query_param":true,"enable_on_yt_command_executor_command_to_navigate":true,"enable_open_in_new_tab_icon_for_short_dr_for_desktop_search":true,"enable_open_yt_content":true,"enable_origin_query_parameter_bugfix":true,"enable_pacf_slot_asde_infeed_h5":true,"enable_pacf_slot_asde_player_byte_h5":true,"enable_pacf_slot_asde_player_byte_h5_TV":true,"enable_panel_ad_header_image_lockup_view_model":true,"enable_payments_purchase_manager":true,"enable_pdp_icon_prefetch":true,"enable_persistent_device_token":true,"enable_pl_r_c_s":true,"enable_pl_r_si_fa":true,"enable_place_pivot_page_entry_point":true,"enable_place_pivot_url":true,"enable_playable_a11y_label_with_badge_text":true,"enable_playable_container_save_button":true,"enable_playable_entity_save_menu_item":true,"enable_playables_desktop_exit_confirmation":true,"enable_playables_url_resolution":true,"enable_poll_choice_border_on_web":true,"enable_polymer_resin":true,"enable_polymer_resin_migration":true,"enable_populate_att_psd_in_abe_feedback":true,"enable_populate_psd_in_abe_feedback":true,"enable_post_cct_links":true,"enable_post_scheduling":true,"enable_premium_voluntary_pause":true,"enable_primitive_dialog_aria_hide_siblings":true,"enable_programmed_playlist_color_sample":true,"enable_programmed_playlist_redesign":true,"enable_purchase_activity_in_paid_memberships":true,"enable_pv_screen_modern_text":true,"enable_quiz_creation":true,"enable_redirect_linking_for_desktop_web_client":true,"enable_reel_watch_sequence":true,"enable_rfa_external_links":true,"enable_rfa_rate_limits":true,"enable_rich_grid_continuation_reflow_fix":true,"enable_rta_manager":true,"enable_sdf_companion_h5":true,"enable_sdf_dai_h5_midroll":true,"enable_sdf_h5_endemic_mid_post_roll":true,"enable_sdf_on_h5_unplugged_vod_midroll":true,"enable_sdf_shorts_player_bytes_h5":true,"enable_sdk_performance_network_logging":true,"enable_secondary_channel_creation_form":true,"enable_seedless_shorts_url":true,"enable_server_stitched_dai":true,"enable_service_ajax_csn":true,"enable_servlet_errors_streamz":true,"enable_servlet_streamz":true,"enable_sfv_audio_pivot_url":true,"enable_sfv_effect_pivot_url":true,"enable_shadydom_free_scoped_node_methods":true,"enable_shadydom_free_scoped_query_methods":true,"enable_shadydom_free_scoped_readonly_properties_batch_one":true,"enable_share_panel_navigation_logging_fix_on_web":true,"enable_short_dr_for_desktop_launchable":true,"enable_skip_ad_guidance_prompt":true,"enable_skippable_ads_for_unplugged_ad_pod":true,"enable_smearing_expansion_dai":true,"enable_sparkles_web_clickable_description":true,"enable_structured_description_shorts_web_mweb":true,"enable_structured_text_list_group_parsing":true,"enable_teaser_framework_web_client":true,"enable_tectonic_ad_ux_for_halftime":true,"enable_third_party_info":true,"enable_time_out_messages":true,"enable_topsoil_wta_for_halftime_live_infra":true,"enable_unavailable_videos_watch_page":true,"enable_unified_cancellation_for_premium":true,"enable_use_cm":true,"enable_use_cm_on_channel":true,"enable_use_cm_on_recognition_shelf":true,"enable_use_cm_on_shorts":true,"enable_use_cm_on_watch":true,"enable_use_cm_panel":true,"enable_variable_timeout_web":true,"enable_video_display_compact_button_group_for_desktop_search":true,"enable_view_i18n_pronouns":true,"enable_view_pronouns_on_main_app":true,"enable_watch_next_pause_autoplay_lact":true,"enable_web_96_bit_csn":true,"enable_web_home_top_landscape_image_layout_level_click":true,"enable_web_ketchup_hero_animation":true,"enable_web_masthead_background_in_ytd_app":true,"enable_web_player_player_in_bar_feature":true,"enable_web_poster_hover_animation":true,"enable_web_shorts_mention_pivot":true,"enable_web_shorts_preview_audio_pivot":true,"enable_web_tiered_gel":true,"enable_window_constrained_buy_flow_dialog":true,"enable_wiz_always_try_logging_info_map":true,"enable_wiz_next_lp2_msof":true,"enable_yoodle":true,"enable_your_playables_feed_entrypoint":true,"enable_ypc_spinners":true,"enable_yt_ata_iframe_authuser":true,"enable_ytc_refunds_submit_form_signal_action":true,"enable_ytc_self_serve_refunds":true,"enable_zero_prefix_mention_suggestion_web":true,"endpoint_handler_logging_cleanup_killswitch":true,"err_on_pl_r_c":true,"export_networkless_options":true,"external_fullscreen":true,"external_fullscreen_with_edu":true,"fill_no_history_user_in_ip":true,"fill_no_history_user_in_watch_status":true,"fill_single_video_with_notify_to_lasr":true,"fix_alignment_search_desktop_text_image_ads_small_screens":true,"formatted_string_remove_child_early_break":true,"gda_enable_playlist_download":true,"global_spacebar_pause":true,"h5_companion_enable_adcpn_macro_substitution_for_click_pings":true,"h5_inplayer_enable_adcpn_macro_substitution_for_click_pings":true,"h5_reset_cache_and_filter_before_update_masthead":true,"handles_in_mention_suggest_posts":true,"hide_endpoint_overflow_on_ytd_display_ad_renderer":true,"html5_log_trigger_events_with_debug_data":true,"html5_offline_playback_position_sync":true,"html5_recognize_predict_start_cue_point":true,"html5_report_supports_vp9_encoding":true,"html5_server_stitched_dai_group":true,"il_attach_cache_limit":true,"il_use_view_model_logging_context":true,"include_autoplay_count_in_playlists":true,"is_part_of_any_user_engagement_experiment":true,"json_condensed_response":true,"kev_adb_pg":true,"kevlar_app_shortcuts":true,"kevlar_appbehavior_attach_startup_tasks":true,"kevlar_autofocus_menu_on_keyboard_nav":true,"kevlar_autonav_popup_filtering":true,"kevlar_av_eliminate_polling":true,"kevlar_cache_cold_load_response":true,"kevlar_cache_on_ttl_player":true,"kevlar_cancel_scheduled_comment_jobs_on_navigate":true,"kevlar_channel_creation_form_resolver":true,"kevlar_channel_trailer_multi_attach":true,"kevlar_chapters_list_view_seek_by_chapter":true,"kevlar_check_current_page_on_stop_old_player":true,"kevlar_clean_feeds_show_more":true,"kevlar_clear_duplicate_pref_cookie":true,"kevlar_clear_non_displayable_url_params":true,"kevlar_client_enable_shorts_player_bootstrap":true,"kevlar_client_side_screens":true,"kevlar_command_handler":true,"kevlar_command_handler_clicks":true,"kevlar_command_handler_formatted_string":true,"kevlar_command_url":true,"kevlar_continue_playback_without_player_response":true,"kevlar_decorate_endpoint_with_onesie_config":true,"kevlar_delay_watch_initial_data":true,"kevlar_disable_background_prefetch":true,"kevlar_disable_pending_command":true,"kevlar_droppable_prefetchable_requests":true,"kevlar_early_popup_close":true,"kevlar_enable_cow_dismissed_mini_game_card":true,"kevlar_enable_editable_playlists":true,"kevlar_enable_mss":true,"kevlar_enable_reorderable_playlists":true,"kevlar_enable_shorts_prefetch_in_sequence":true,"kevlar_enable_shorts_response_chunking":true,"kevlar_enable_up_arrow":true,"kevlar_enable_wiz_icon_shape":true,"kevlar_exit_fullscreen_leaving_watch":true,"kevlar_fetch_pbj":true,"kevlar_fix_playlist_continuation":true,"kevlar_flexible_menu":true,"kevlar_flexy_use_larger_player_value":true,"kevlar_fluid_touch_scroll":true,"kevlar_gel_error_routing":true,"kevlar_guide_refresh":true,"kevlar_help_use_locale":true,"kevlar_hide_pp_url_param":true,"kevlar_hide_time_continue_url_param":true,"kevlar_home_skeleton":true,"kevlar_keyboard_button_focus":true,"kevlar_larger_three_dot_tap":true,"kevlar_lazy_list_resume_for_autofill":true,"kevlar_legacy_browsers":true,"kevlar_local_innertube_response":true,"kevlar_log_updated_time_for_smart_downloads":true,"kevlar_macro_markers_keyboard_shortcut":true,"kevlar_mandatory_icon_data_killswitch":true,"kevlar_masthead_store":true,"kevlar_mealbar_above_player":true,"kevlar_mini_app_container_c3po_to_wiz":true,"kevlar_mini_game_card_c3po_to_wiz":true,"kevlar_miniplayer_expand_top":true,"kevlar_miniplayer_play_pause_on_scrim":true,"kevlar_miniplayer_queue_user_activation":true,"kevlar_mix_handle_first_endpoint_different":true,"kevlar_modern_sd":true,"kevlar_modern_sd_v2":true,"kevlar_next_cold_on_auth_change_detected":true,"kevlar_nitrate_driven_tooltips":true,"kevlar_no_autoscroll_on_playlist_hover":true,"kevlar_op_infra":true,"kevlar_op_warm_pages":true,"kevlar_passive_event_listeners":true,"kevlar_playback_associated_queue":true,"kevlar_player_cached_load_config":true,"kevlar_player_check_ad_state_on_stop":true,"kevlar_player_load_player_no_op":true,"kevlar_player_new_bootstrap_adoption":true,"kevlar_player_playlist_use_local_index":true,"kevlar_player_watch_endpoint_navigation":true,"kevlar_playlist_drag_handles":true,"kevlar_playlist_use_x_close_button":true,"kevlar_poly_si_batch_j007":true,"kevlar_poly_si_batch_j007_holdback_enabled":true,"kevlar_poly_si_batch_j020":true,"kevlar_poly_si_batch_j020_holdback_enabled":true,"kevlar_poly_si_batch_j022":true,"kevlar_poly_si_batch_j022_holdback_enabled":true,"kevlar_poly_si_batch_j031":true,"kevlar_poly_si_batch_j031_holdback_enabled":true,"kevlar_poly_si_batch_j032":true,"kevlar_poly_si_batch_j032_holdback_enabled":true,"kevlar_poly_si_batch_j033":true,"kevlar_poly_si_batch_j033_holdback_enabled":true,"kevlar_poly_si_batch_j034":true,"kevlar_poly_si_batch_j034_holdback_enabled":true,"kevlar_poly_si_batch_j037":true,"kevlar_poly_si_batch_j037_holdback_enabled":true,"kevlar_poly_si_batch_j038":true,"kevlar_poly_si_batch_j038_holdback_enabled":true,"kevlar_poly_si_batch_j040":true,"kevlar_poly_si_batch_j040_holdback_enabled":true,"kevlar_poly_si_batch_j041":true,"kevlar_poly_si_batch_j041_holdback_enabled":true,"kevlar_poly_si_batch_j042":true,"kevlar_poly_si_batch_j042_holdback_enabled":true,"kevlar_poly_si_batch_j043":true,"kevlar_poly_si_batch_j043_holdback_enabled":true,"kevlar_poly_si_batch_j044":true,"kevlar_poly_si_batch_j044_holdback_enabled":true,"kevlar_poly_si_batch_j045":true,"kevlar_poly_si_batch_j045_holdback_enabled":true,"kevlar_poly_si_batch_j046":true,"kevlar_poly_si_batch_j046_holdback_enabled":true,"kevlar_poly_si_batch_j048":true,"kevlar_poly_si_batch_j048_holdback_enabled":true,"kevlar_poly_si_batch_j049":true,"kevlar_poly_si_batch_j049_holdback_enabled":true,"kevlar_poly_si_batch_j050":true,"kevlar_poly_si_batch_j050_holdback_enabled":true,"kevlar_poly_si_batch_j051":true,"kevlar_poly_si_batch_j051_holdback_enabled":true,"kevlar_poly_si_batch_j052":true,"kevlar_poly_si_batch_j052_holdback_enabled":true,"kevlar_poly_si_batch_j054":true,"kevlar_poly_si_batch_j054_holdback_enabled":true,"kevlar_poly_si_batch_j055":true,"kevlar_poly_si_batch_j055_holdback_enabled":true,"kevlar_poly_si_batch_j056":true,"kevlar_poly_si_batch_j056_holdback_enabled":true,"kevlar_poly_si_batch_j057":true,"kevlar_poly_si_batch_j057_holdback_enabled":true,"kevlar_poly_si_batch_j059":true,"kevlar_poly_si_batch_j059_holdback_enabled":true,"kevlar_poly_si_batch_j060":true,"kevlar_poly_si_batch_j060_holdback_enabled":true,"kevlar_poly_si_batch_j061":true,"kevlar_poly_si_batch_j061_holdback_enabled":true,"kevlar_poly_si_batch_j062":true,"kevlar_poly_si_batch_j062_holdback_enabled":true,"kevlar_poly_si_batch_j064":true,"kevlar_poly_si_batch_j064_holdback_enabled":true,"kevlar_poly_si_batch_j066":true,"kevlar_poly_si_batch_j066_holdback_enabled":true,"kevlar_poly_si_batch_j068":true,"kevlar_poly_si_batch_j068_holdback_enabled":true,"kevlar_poly_si_batch_j069":true,"kevlar_poly_si_batch_j069_holdback_enabled":true,"kevlar_poly_si_batch_j071":true,"kevlar_poly_si_batch_j071_holdback_enabled":true,"kevlar_prefetch":true,"kevlar_prevent_polymer_dynamic_font_load":true,"kevlar_rendererstamper_event_listener":true,"kevlar_request_sequencing":true,"kevlar_response_command_processor_page":true,"kevlar_service_command_check":true,"kevlar_set_internal_player_size":true,"kevlar_shell_for_downloads_page":true,"kevlar_shorts_seedless_retry_initial_load":true,"kevlar_should_maintain_stable_list":true,"kevlar_show_playlist_dl_btn":true,"kevlar_sign_in_modal_modernization":true,"kevlar_structured_description_content_inline":true,"kevlar_system_icons":true,"kevlar_text_inline_expander_formatted_snippet":true,"kevlar_thumbnail_fluid":true,"kevlar_topbar_logo_fallback_home":true,"kevlar_touch_feedback":true,"kevlar_transcript_engagement_panel":true,"kevlar_tuner_run_default_comments_delay":true,"kevlar_tuner_should_defer_detach":true,"kevlar_typography_spacing_update":true,"kevlar_typography_update":true,"kevlar_unified_errors_init":true,"kevlar_use_response_ttl_to_invalidate_cache":true,"kevlar_use_vimio_behavior":true,"kevlar_use_wil_icons":true,"kevlar_use_ytd_player":true,"kevlar_vimio_use_shared_monitor":true,"kevlar_voice_logging_fix":true,"kevlar_voice_search_use_yt_endpoint":true,"kevlar_watch_cinematics":true,"kevlar_watch_color_update":true,"kevlar_watch_comments_ep_disable_theater":true,"kevlar_watch_drag_handles":true,"kevlar_watch_flexy_comments_manager":true,"kevlar_watch_flexy_miniplayer_manager":true,"kevlar_watch_flexy_playlist_manager":true,"kevlar_watch_flexy_theater_manager":true,"kevlar_watch_flexy_use_controller":true,"kevlar_watch_focus_on_engagement_panels":true,"kevlar_watch_hide_comments_teaser":true,"kevlar_watch_hide_comments_while_panel_open":true,"kevlar_watch_js_panel_height":true,"kevlar_watch_metadata_refresh":true,"kevlar_watch_metadata_refresh_attached_subscribe":true,"kevlar_watch_metadata_refresh_clickable_description":true,"kevlar_watch_metadata_refresh_compact_view_count":true,"kevlar_watch_metadata_refresh_description_info_dedicated_line":true,"kevlar_watch_metadata_refresh_description_inline_expander":true,"kevlar_watch_metadata_refresh_description_primary_color":true,"kevlar_watch_metadata_refresh_for_live_killswitch":true,"kevlar_watch_metadata_refresh_full_width_description":true,"kevlar_watch_metadata_refresh_left_aligned_video_actions":true,"kevlar_watch_metadata_refresh_lower_case_video_actions":true,"kevlar_watch_metadata_refresh_narrower_item_wrap":true,"kevlar_watch_metadata_refresh_no_old_secondary_data":true,"kevlar_watch_metadata_refresh_relative_date":true,"kevlar_watch_metadata_refresh_top_aligned_actions":true,"kevlar_watch_modern_metapanel":true,"kevlar_watch_modern_panels":true,"kevlar_wiz_player_microformat":true,"kevlar_woffle_fallback_image":true,"kevlar_woffle_log_thumbnail_failure_ve":true,"kevlar_ytb_live_badges":true,"killswitch_toggle_button_behavior_resolve_command":true,"live_chat_compute_chat_mode_selection_aria_label":true,"live_chat_cow_init_error_handling":true,"live_chat_cow_visibility_set_up":true,"live_chat_emoji_picker_toggle_state":true,"live_chat_enable_controller_extraction":true,"live_chat_enable_pagehide_listener":true,"live_chat_enable_rta_manager":true,"live_chat_enable_send_button_in_slow_mode":true,"live_chat_increased_min_height":true,"live_chat_over_playlist":true,"live_chat_require_space_for_autocomplete_emoji":true,"live_chat_web_use_emoji_manager_singleton":true,"live_chat_whole_message_clickable":true,"log_click_with_layer_from_element_in_command_handler":true,"log_errors_through_nwl_on_retry":true,"log_gel_compression_latency":true,"log_heartbeat_with_lifecycles":true,"main_web_redirect_integration_riot":true,"mdx_enable_privacy_disclosure_ui":true,"mdx_load_cast_api_bootstrap_script":true,"migrate_desktop_pdp_to_posts_frontend_service":true,"migrate_events_to_ts":true,"migrate_remaining_web_ad_badges_to_innertube":true,"miniplayer_service_controls_disabling":true,"modernize_structured_description_playlist_lockups_v2":true,"music_on_main_open_playlist_recommended_videos_in_miniplayer":true,"mweb_a11y_enable_player_controls_invisible_toggle":true,"mweb_account_linking_noapp":true,"mweb_actions_command_handler":true,"mweb_attr_string_wiz":true,"mweb_c3_remove_web_navigation_endpoint_data":true,"mweb_channels_restyle_buttons_extra_padding":true,"mweb_channels_restyle_landscape_wider_buttons":true,"mweb_command_handler":true,"mweb_deprecate_skip_ve_logging":true,"mweb_enable_local_innertube_services":true,"mweb_enable_search_big_thumbs":true,"mweb_enable_varispeed_controller":true,"mweb_like_endpoint_mutation":true,"mweb_logo_use_home_page_ve":true,"mweb_paid_content_overlay_override_click_killswitch":true,"mweb_shorts_comments_panel_id_change":true,"mweb_stop_truncating_meta_tags":true,"mweb_use_server_url_on_startup":true,"mweb_watch_captions_enable_auto_translate":true,"network_status_banner_display_none":true,"networkless_gel":true,"networkless_logging":true,"new_csn_storage_design":true,"nwl_send_fast_on_unload":true,"nwl_send_from_memory_when_online":true,"offline_error_handling":true,"owl_pl_await":true,"pageid_as_header_web":true,"par_at_ep":true,"pause_ad_video_on_desktop_engagement_panel_click":true,"pdg_enable_flow_logging_for_super_chat":true,"pdg_enable_flow_logging_for_super_stickers":true,"player_bootstrap_method":true,"player_doubletap_to_seek":true,"player_enable_playback_playlist_change":true,"polymer2_not_shady_build":true,"polymer_bad_build_labels":true,"polymer_verifiy_app_state":true,"polymer_ytdi_enable_global_injector":true,"prevent_zero_high_score_value_from_being_sent":true,"problem_walkthrough_sd":true,"qoe_send_and_write":true,"read_data_from_web_component_wrapper":true,"record_app_crashed_web":true,"reel_watch_sequence_request_log_ticks":true,"reels_enable_arrow_button_tooltip":true,"reels_enable_early_continuation":true,"reels_enable_new_latency_logging":true,"reels_web_use_pbs_first_shorts":true,"register_web_smartimations_component":true,"reload_without_polymer_innertube":true,"remove_masthead_channel_banner_on_refresh":true,"remove_slot_id_exited_trigger_for_dai_in_player_slot_expire":true,"remove_yt_simple_endpoint_from_desktop_display_ad_title":true,"replace_client_url_parsing_with_server_signal":true,"rich_grid_enable_dynamic_offset":true,"scheduler_use_raf_by_default":true,"search_ui_enable_pve_buy_button":true,"search_ui_official_cards_enable_paid_virtual_event_buy_button":true,"searchbox_reporting":true,"serve_pdp_at_canonical_url":true,"service_worker_enabled":true,"service_worker_push_enabled":true,"service_worker_push_home_page_prompt":true,"service_worker_push_watch_page_prompt":true,"service_worker_static_routing_exclude_embed":true,"service_worker_static_routing_registration":true,"service_worker_subscribe_with_vapid_key":true,"shared_enable_controller_extraction":true,"shared_enable_sink_wrapping":true,"shell_load_gcf":true,"shorts_desktop_watch_while_p2":true,"shorts_desktop_watch_while_sdp":true,"shorts_in_playlists_web":true,"shorts_overlay_reshuffle":true,"should_clear_video_data_on_player_cued_unstarted":true,"show_ghost_comments_shorts_watch":true,"skip_invalid_ytcsi_ticks":true,"skip_ls_gel_retry":true,"skip_setting_info_in_csi_data_object":true,"smarter_ve_dedupping":true,"smartimation_background":true,"sponsorships_free_creator_gifting":true,"start_client_gcf":true,"suppress_error_204_logging":true,"swap_open_in_new_with_arrow_diagonal_up_right_on_desktop":true,"swatcheroo_ep_use_inline_expander":true,"transport_use_scheduler":true,"use_ads_engagement_panel_desktop_footer_cta":true,"use_better_post_dismissals":true,"use_border_and_grid_wrapping_on_desktop_panel_tiles":true,"use_color_palettes_modern_collections_v2":true,"use_core_sm":true,"use_csi_stp_handler":true,"use_event_time_ms_header":true,"use_fifo_for_networkless":true,"use_hide_cm_button_on_watch":true,"use_infogel_early_logging":true,"use_new_in_memory_storage":true,"use_not_now_dl_upsell_dismiss_cta":true,"use_player_abuse_bg_library":true,"use_request_time_ms_header":true,"use_session_based_sampling":true,"use_ts_visibilitylogger":true,"use_unified_flag_accessor":true,"use_watch_fragments2":true,"use_ytd_player_for_desktop_masthead":true,"vss_final_ping_send_and_write":true,"vss_playback_use_send_and_write":true,"warm_load_nav_start_web":true,"warm_op_csn_cleanup":true,"web_action_buttons_overlap_fix":true,"web_ad_card_buttoned_clickable":true,"web_ad_metadata_clickable":true,"web_always_load_chat_support":true,"web_amsterdam_post_mvp_playlists":true,"web_animated_actions":true,"web_animated_actions_v2":true,"web_animated_like":true,"web_animated_rolling_character_update":true,"web_api_url":true,"web_autonav_allow_off_by_default":true,"web_bookmark_playlist_save_icon":true,"web_button_rework":true,"web_button_rework_with_live":true,"web_cairo_modern_miniplayer":true,"web_cairo_modern_miniplayer_expand_transitions":true,"web_cairo_modern_miniplayer_fade_transitions":true,"web_cairo_modern_miniplayer_infobar":true,"web_cairo_modern_miniplayer_old_sizing":true,"web_cairo_modern_miniplayer_transitions":true,"web_chat_prevent_chat_header_overflow":true,"web_chip_shape_defrag":true,"web_cinematic_light_theme":true,"web_collab_playlist_thumbnail_size":true,"web_consolidated_panel":true,"web_continuation_response_processing":true,"web_csi_action_sampling_enabled":true,"web_csi_debug_sample_enabled":true,"web_darker_dark_theme":true,"web_darker_dark_theme_deprecate":true,"web_darker_dark_theme_live_chat":true,"web_dedupe_ve_grafting":true,"web_defer_shorts_ui":true,"web_defer_shorts_ui_phase2":true,"web_direct_inject_in_select":true,"web_disable_cache_for_guide_home":true,"web_disable_channels_chapter_entrypoint":true,"web_disable_page_navigation_progress_for_shorts":true,"web_early_logging_time_init":true,"web_enable_ab_em_rsp":true,"web_enable_ab_rsp_cl":true,"web_enable_abd_ref":true,"web_enable_adaptive_appl_signal":true,"web_enable_constrained_list_subscriptions_channels":true,"web_enable_course_icon_update":true,"web_enable_deeper_metadata_highlight_styling":true,"web_enable_dynamic_metadata":true,"web_enable_dynamic_suggested_action":true,"web_enable_error_204":true,"web_enable_feedback_endpoint_resolver":true,"web_enable_feedback_endpoint_resolver_batch_2":true,"web_enable_history_cache_map":true,"web_enable_history_sandbox":true,"web_enable_horizontal_video_attributes_section":true,"web_enable_invite_collaborators_link":true,"web_enable_keyboard_shortcut_for_timely_actions":true,"web_enable_miniplayer_error_screen":true,"web_enable_miniplayer_refactor":true,"web_enable_shorts_new_carousel_single_instance":true,"web_enable_sink_animated_actions":true,"web_enable_sink_avatars_batch":true,"web_enable_sink_checkbox_shape":true,"web_enable_sink_dialog_header_view_model":true,"web_enable_sink_list_view_models":true,"web_enable_sink_lockups_batch":true,"web_enable_sink_page_header_view_model":true,"web_enable_sink_radio_shape":true,"web_enable_sink_section_header":true,"web_enable_sink_smartimations":true,"web_enable_sink_video_summary_content_view_model":true,"web_enable_sink_yt_description_preview_view_model":true,"web_enable_sink_yt_page_header_renderer":true,"web_enable_sink_yt_subscribe_button_view_model":true,"web_enable_timely_actions":true,"web_enable_voting_animation":true,"web_enable_youtab":true,"web_engagement_panel_show_description":true,"web_ep_restyling":true,"web_ephemeral_actions":true,"web_fill_shorts_detailed_accessibility":true,"web_filled_subscribed_button":true,"web_fix_back_button_player_loading":true,"web_fix_dynamic_metadata_diacritic":true,"web_fix_fine_scrubbing_false_play":true,"web_fix_missing_action_buttons":true,"web_fix_segmented_like_dislike_undefined":true,"web_forward_command_on_pbj":true,"web_frosted_glass":true,"web_frosted_glass_disable_watch":true,"web_fullscreen_shorts":true,"web_gcf_hashes_innertube":true,"web_gel_timeout_cap":true,"web_get_updated_metadata_fantasy_panel_continuation_params":true,"web_guide_ui_refresh":true,"web_header_eu_about_these_results":true,"web_hide_autonav_keyline":true,"web_home_appeal_survey":true,"web_home_reflow_options_tuning":true,"web_honor_cache_for_back":true,"web_infocards_teaser_show_logging_fix":true,"web_inline_player_enabled":true,"web_kevlar_enable_adaptive_signals":true,"web_log_feedback_on_submitted":true,"web_log_memory_total_kbytes":true,"web_log_player_watch_next_ticks":true,"web_log_reels_ticks":true,"web_masthead_visited_channel_color_fix":true,"web_memoize_inflight_requests":true,"web_modern_ads":true,"web_modern_buttons":true,"web_modern_buttons_bl_survey":true,"web_modern_chips":true,"web_modern_collections":true,"web_modern_collections_v2":true,"web_modern_dialogs":true,"web_modern_playlists":true,"web_modern_surveys":true,"web_modern_tabs":true,"web_modern_typography":true,"web_modern_vwt_surveys":true,"web_modern_vwt_surveys_sampled":true,"web_modern_vwt_surveys_sampled_unclickable_video":true,"web_modern_vwt_surveys_v2":true,"web_move_autoplay_video_under_chip":true,"web_no_bottom_margin_for_home_ad_buttons":true,"web_no_skip_video_on_verification_checks":true,"web_now_playing_badge":true,"web_one_platform_error_handling":true,"web_persist_server_autonav_state_on_client":true,"web_player_audio_playback_from_audio_config":true,"web_player_autonav_empty_suggestions_fix":true,"web_player_autonav_next_button_renderer":true,"web_player_autonav_toggle_always_listen":true,"web_player_autonav_use_server_provided_state":true,"web_player_disable_inline_scrubbing":true,"web_player_enable_cultural_moment_overlay":true,"web_player_enable_featured_product_banner_exclusives_on_desktop":true,"web_player_enable_featured_product_banner_promotion_text_on_desktop":true,"web_player_enable_overflow_button_in_banner_on_desktop":true,"web_player_enable_premium_hbr_in_h5_api":true,"web_player_enable_premium_hbr_playback_cap":true,"web_player_entities_middleware":true,"web_player_log_click_before_generating_ve_conversion_params":true,"web_player_move_autonav_toggle":true,"web_player_rtr_ctrls":true,"web_player_shorts_audio_pivot_event_label":true,"web_player_should_honor_include_asr_setting":true,"web_player_small_hbp_settings_menu":true,"web_player_split_event_bus":true,"web_player_use_heartbeat_poll_delay_ms":true,"web_player_use_new_api_for_quality_pullback":true,"web_player_ve_conversion_fixes_for_channel_info":true,"web_poly_si_remove_using":true,"web_prefetch_preload_video":true,"web_prs_testing_mode_killswitch":true,"web_reels_enable_audio_pivot_in_engagement_panel":true,"web_reels_reduce_shorts_margin":true,"web_rendererstamper_event_listener":true,"web_replace_thumbnail_with_image":true,"web_resizable_advertiser_banner_on_masthead_safari_fix":true,"web_responsive_current":true,"web_rich_grid_standard_shelf_margins":true,"web_rich_shelf_show_more_button":true,"web_rounded_thumbnails":true,"web_scheduler_auto_init":true,"web_scroll_into_view_center":true,"web_see_fewer_shorts_on_shelf":true,"web_see_fewer_shorts_reactive_dismissal":true,"web_segmented_like_dislike_button":true,"web_settings_menu_surface_custom_playback":true,"web_settings_use_input_slider":true,"web_sheets_ui_refresh":true,"web_shorts_2_by_3_lockups_study":true,"web_shorts_cinematic":true,"web_shorts_cinematic_scrim":true,"web_shorts_comment_stickers":true,"web_shorts_deflate_ads_overlay":true,"web_shorts_deflate_inactive_slides_aggressive":true,"web_shorts_dynamic_intersection_observer_threshold":true,"web_shorts_enable_title_deflation":true,"web_shorts_fallback_sticker":true,"web_shorts_lockup_view_model_sink":true,"web_shorts_pivot_button_view_model_reactive":true,"web_shorts_scrubber_bar":true,"web_shorts_shelf_on_search":true,"web_shorts_skip_loading_same_index":true,"web_shorts_small_screen_watch_while":true,"web_shorts_sound_metadata":true,"web_shorts_suggested_action_no_bvm":true,"web_shorts_surveys":true,"web_skip_missing_vimio_observer":true,"web_sleep_timer":true,"web_speedmaster_spacebar_control":true,"web_structured_description_show_more":true,"web_suggestion_box_bolder":true,"web_suggestion_box_restyle":true,"web_supports_animations_api":true,"web_timed_sync_manager":true,"web_timeline_view":true,"web_touch_feedback_changes":true,"web_update_flexible_buttons_after_renderidom":true,"web_use_cache_for_image_fallback":true,"web_use_layout_library_for_rich_grid":true,"web_use_updated_icon_for_oac_badge":true,"web_vision_pro_detection_killswitch":true,"web_watch_add_viewport_meta_tag":true,"web_watch_chips_mask_fade":true,"web_watch_cinematics_preferred_reduced_motion_default_disabled":true,"web_watch_eligible_to_switch_to_grid":true,"web_watch_enable_single_column_grid_view":true,"web_watch_fill_description_more_text":true,"web_watch_get_updated_metadata_manager":true,"web_watch_get_updated_metadata_response_processing":true,"web_watch_provide_undefined_comments":true,"web_watch_rounded_player_large":true,"web_watch_theater_chat":true,"web_watch_theater_fixed_chat":true,"web_watch_typography_title_headline_xs":true,"web_watch_updated_metadata_server_initial_delay":true,"web_yt_config_context":true,"web_yt_searchbox":true,"webfe_disable_ab_em_plb":true,"webfe_preload_attc":true,"webfe_use_enf_mod":true,"wil_icon_render_when_idle":true,"wiz_diff_overwritable":true,"wiz_memoize_stamper_items":true,"wiz_use_generic_logging_infra":true,"woffle_clean_up_after_entity_migration":true,"woffle_enable_download_status":true,"woffle_playlist_optimization":true,"woffle_playlist_visitor_fix":true,"woffle_used_state_report":true,"wp_lat_b":true,"ww_squeezeback_killswitch":true,"yt_network_manager_component_to_lib_killswitch":true,"ytd_miniplayer_toast_to_wiz":true,"ytidb_clear_embedded_player":true,"ytidb_fetch_datasync_ids_for_data_cleanup":true,"H5_async_logging_delay_ms":30000.0,"app_shell_asset_log_fraction":0.01,"autoplay_pause_by_lact_sampling_fraction":0.0,"cinematic_watch_effect_opacity":0.4,"composed_path_browser_fallback_warning_threshold":0.1,"dynamic_metadata_update_interaction_delay_period_sec":5.0,"formatted_description_log_warning_fraction":0.01,"kevlar_tuner_clamp_device_pixel_ratio":2.0,"kevlar_tuner_thumbnail_factor":1.0,"kevlar_unified_player_logging_threshold":1.0,"log_window_onerror_fraction":0.1,"miniplayer_refactor_threshold_flag":1.0,"miniplayer_visibility_threshold_flag":1.0,"pbcm_fi_query_decorators_fallback_logging_pct":0.0,"polymer_property_access_logging_percent":0.0,"polymer_report_client_url_requested_rate":0.001,"polymer_report_missing_web_navigation_endpoint_rate":0.001,"prefetch_coordinator_error_logging_sampling_rate":1.0,"tv_pacf_logging_sample_rate":0.01,"web_pbj_log_warning_rate":0.0,"web_shorts_error_logging_threshold":0.001,"web_shorts_intersection_observer_threshold_override":0.0,"web_system_health_fraction":0.01,"ytidb_transaction_ended_event_rate_limit":0.02,"active_time_update_interval_ms":10000,"autoplay_pause_by_lact_sec":0,"autoplay_time":8000,"autoplay_time_for_fullscreen":3000,"autoplay_time_for_music_content":3000,"botguard_async_snapshot_timeout_ms":3000,"check_navigator_accuracy_timeout_ms":0,"cinematic_watch_css_filter_blur_strength":40,"cinematic_watch_fade_out_duration":500,"cinematic_watch_transition_frame_rate":15,"client_streamz_web_flush_count":100,"client_streamz_web_flush_interval_seconds":60,"close_webview_delay_ms":100,"cloud_save_game_data_rate_limit_ms":3000,"compression_disable_point":10,"custom_active_view_tos_timeout_ms":3600000,"delayed_cta_on_web_delay_milliseconds":1000,"desktop_fountain_emoji_size_px":20,"desktop_search_suggestion_tap_target":0,"desktop_shorts_grid_shelf_max_items_per_row":5,"desktop_suggestions_panel_row_height":44,"enable_shorts_panel_show_delay_ms":0,"external_fullscreen_button_click_threshold":2,"external_fullscreen_button_shown_threshold":10,"gel_min_batch_size":3,"gel_queue_timeout_max_ms":300000,"get_async_timeout_ms":60000,"hide_cta_for_home_web_video_ads_animate_in_time":2,"high_priority_flyout_frequency":3,"initial_gel_batch_timeout":2000,"kevlar_lockup_hover_delay":32,"kevlar_mini_guide_width_threshold":791,"kevlar_persistent_guide_width_threshold":1312,"kevlar_time_caching_end_threshold":15,"kevlar_time_caching_start_threshold":15,"kevlar_tooltip_impression_cap":2,"kevlar_tuner_default_comments_delay":1000,"kevlar_tuner_scheduler_soft_state_timer_ms":800,"kevlar_tuner_visibility_time_between_jobs_ms":100,"kevlar_watch_flexy_metadata_height":136,"kevlar_watch_grid_sidebar_min_width":300,"kevlar_watch_max_player_width":1840,"kevlar_watch_page_columns_top_padding":24,"kevlar_watch_page_horizontal_margin":24,"kevlar_watch_player_min_height":360,"kevlar_watch_secondary_width":402,"kevlar_watch_two_column_width_threshold":1000,"live_chat_chunk_rendering":0,"live_chat_chunking_qps_threshold":-1,"live_chat_max_chunk_size":5,"live_chat_min_chunk_interval_ms":300,"max_body_size_to_compress":500000,"max_num_images_per_post_web_ui":5,"max_prefetch_window_sec_for_livestream_optimization":10,"min_prefetch_offset_sec_for_livestream_optimization":20,"mweb_history_manager_cache_size":100,"mweb_history_manager_w2w_ttl":0,"network_polling_interval":30000,"pbj_navigate_limit":-1,"play_click_interval_ms":30000,"play_ping_interval_ms":10000,"prefetch_comments_ms_after_video":0,"prefetch_coordinator_command_timeout_ms":60000,"prefetch_coordinator_max_inflight_requests":1,"reels_enable_early_continuation_offset":1,"rich_grid_dynamic_offset_anchor_width":1200,"rich_grid_dynamic_offset_lower_bound":-40,"rich_grid_dynamic_offset_offset_per_100":20,"rich_grid_max_item_width":700,"rich_grid_min_item_width":310,"send_config_hash_timer":0,"service_worker_push_logged_out_prompt_watches":-1,"service_worker_push_prompt_cap":-1,"service_worker_push_prompt_delay_microseconds":3888000000000,"show_mini_app_ad_frequency_cap_ms":300000,"slow_compressions_before_abandon_count":4,"swatcheroo_pbs_max_delay_ms":3000,"swatcheroo_provide_all_data_delay_ms":250,"swatcheroo_rich_grid_delay":0,"teaser_carousel_desc_lines":2,"user_engagement_experiments_rate_limit_ms":86400000,"user_mention_suggestions_edu_impression_cap":10,"visibility_time_between_jobs_ms":100,"web_cold_open_animation_initial_delay":2000,"web_continuation_handler_margin":0,"web_emulated_idle_callback_delay":0,"web_foreground_heartbeat_interval_ms":28000,"web_gel_debounce_ms":60000,"web_gnome_max_shelf_depth":1,"web_home_min_first_row_for_groups":0,"web_home_minimum_rows_of_videos_at_start":1,"web_home_minimum_rows_of_videos_at_start_client":2,"web_home_minimum_rows_of_videos_between_sections":1,"web_logging_max_batch":150,"web_max_tracing_events":50,"web_page_transition_enter_ms":200,"web_page_transition_exit_ms":50,"web_page_transition_wait_ms":50,"web_reels_enable_audio_pivot_in_panel":1,"web_rich_grid_row_margin":32,"web_shorts_deflate_inactive_slides_aggressive_distance":2,"web_shorts_prefetch_service_ttl_seconds":600,"web_swipe_animation_time":300,"wil_icon_max_concurrent_fetches":9999,"wn_grid_max_item_width":0,"wn_grid_min_item_width":0,"yoodle_end_time_utc":0,"yoodle_start_time_utc":0,"ytidb_remake_db_retries":1,"ytidb_reopen_db_retries":0,"WebClientReleaseProcessCritical__youtube_web_client_version_override":"","debug_forced_internalcountrycode":"","desktop_search_bigger_thumbs_style":"BIG","desktop_searchbar_style":"default","embeds_web_synth_ch_headers_banned_urls_regex":"","il_payload_scraping":"","kevlar_duplicate_pref_cookie_domain_override":"","kevlar_link_capturing_mode":"","live_chat_unicode_emoji_json_url":"https://www.gstatic.com/youtube/img/emojis/emojis-png-15.1.json","loading_bar_progress_calculation":"logarithmic","place_pivot_triggering_container_alternate":"9:trigger:legos","place_pivot_triggering_counterfactual_container_alternate":"","podcast_show_lockup_style_web":"","polymer_task_manager_status":"production","reels_action_justified_content":"flex-start","reels_metadata_justified_content":"flex-start","reels_navigation_justify_content":"center","search_overview_link_style":"green_track","service_worker_push_force_notification_prompt_tag":"1","service_worker_scope":"/","talk_to_recs_question_response_ui":"DEFAULT","web_client_version_override":"","web_home_feed_reload_experience":"none","web_hw_pb_panel_json_stringified":"","web_page_transition_type":"","web_shorts_expanded_overlay_type":"DEFAULT","web_shorts_overlay_vertical_orientation":"bottom","webfe_reporting_endpoints_urls":"default\u003d\"/error_204\"","yoodle_base_url":"","guide_business_info_countries":["KR"],"guide_legal_footer_enabled_countries":["NL","ES"],"html5_profiler_trace_enums":[],"kevlar_command_handler_command_banlist":[],"kevlar_new_stop_old_player_logic_pages":[],"kevlar_page_service_url_prefix_carveouts":[],"live_chat_dynamic_chunking_interval_range":[],"live_chat_dynamic_chunking_traffic_range":[],"mini_app_ids_without_game_ready":["UgkxHHtsak1SC8mRGHMZewc4HzeAY3yhPPmJ","Ugkx7OgzFqE6z_5Mtf4YsotGfQNII1DF_RBm"],"web_op_signal_type_banlist":[]},"GAPI_HINT_PARAMS":"m;/_/scs/abc-static/_/js/k\u003dgapi.gapi.en.citSWp3NP7U.O/d\u003d1/rs\u003dAHpOoo9xL6HUJcSIDSbTUlNBOsamhv5RMA/m\u003d__features__","GAPI_HOST":"https://apis.google.com","GAPI_LOCALE":"en_US","GL":"DE","GOOGLE_FEEDBACK_PRODUCT_ID":"59","GOOGLE_FEEDBACK_PRODUCT_DATA":{"polymer":"active","polymer2":"active","accept_language":"en-US"},"HL":"en","HTML_DIR":"ltr","HTML_LANG":"en","INNERTUBE_API_KEY":"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8","INNERTUBE_API_VERSION":"v1","INNERTUBE_CLIENT_NAME":"WEB","INNERTUBE_CLIENT_VERSION":"2.20250610.00.00","INNERTUBE_CONTEXT":{"client":{"hl":"en","gl":"DE","remoteHost":"2a02:908:1991:1ec0:9:870c:a854:53fe","deviceMake":"Robot","deviceModel":"Bot or Crawler","visitorData":"CgtybUlNRDEtNnA3OCjFu6bCBjIKCgJERRIEEgAgOw%3D%3D","userAgent":"curl/8.7.1,gzip(gfe)","clientName":"WEB","clientVersion":"2.20250610.00.00","osVersion":"","originalUrl":"https://www.youtube.com/watch?v\u003dGJLlxj_dtq8","platform":"DESKTOP","clientFormFactor":"UNKNOWN_FORM_FACTOR","configInfo":{"appInstallData":"CMW7psIGELfq_hIQvbauBRCwic8cEIW0zxwQpK7PHBCa9M4cEJOGzxwQ6aHPHBC9mbAFENSazxwQ3rzOHBD8ss4cEJmYsQUQyfevBRC-irAFEImwzhwQgc3OHBDN0bEFEIiEuCIQ39zOHBDXwbEFEInorgUQtq3PHBCIh7AFEIKPzxwQ9quwBRDtoM8cEIGgzxwQ56_PHBDi1K4FEIygzxwQr4bPHBDL0bEFEJT-sAUQntCwBRC4n88cEMzfrgUQq53PHBCEkM8cENOfzxwQyaXPHBCliIATENeczxwQvK7PHBDpiM8cEODg_xIQw4qAExDgzbEFEJ2msAUQjcywBRCjr88cELvZzhwQudnOHBDMic8cELuczxwQo67PHBC45M4cEKaasAUQ9v7_EhDlrs8cEIeszhwQ8OLOHBDbr68FEIjjrwUQ0-GvBRDbh4ATENr3zhwQi4KAExDr6P4SEJmNsQUQjqzPHBD98_8SEKv4zhwQ0abPHBDLss8cEICXzxwQ4JzPHBCBlIATKjhDQU1TSlJVbW9MMndETkhrQnBTQ0V1Zmk1Z3VQOUE3di13YktJUGlZQUlNenRjd0cxcDBGSFFjPQ%3D%3D"},"userInterfaceTheme":"USER_INTERFACE_THEME_LIGHT","acceptHeader":"*/*","deviceExperimentId":"ChxOelV4TkRjeE1Ea3lORE14TWpFMk5EVXpOUT09EMW7psIGGMW7psIG","rolloutToken":"CKqS6dO3h8ig_wEQzJ2U3dTpjQMYzJ2U3dTpjQM%3D"},"user":{"lockedSafetyMode":false},"request":{"useSsl":true},"clickTracking":{"clickTrackingParams":"IhMIxIiU3dTpjQMVKoyMCB0gQi6E"}},"INNERTUBE_CONTEXT_CLIENT_NAME":1,"INNERTUBE_CONTEXT_CLIENT_VERSION":"2.20250610.00.00","INNERTUBE_CONTEXT_GL":"DE","INNERTUBE_CONTEXT_HL":"en","LATEST_ECATCHER_SERVICE_TRACKING_PARAMS":{"client.name":"WEB"},"LOGGED_IN":false,"PAGE_BUILD_LABEL":"youtube.desktop.web_20250610_00_RC00","PAGE_CL":769437053,"scheduler":{"useRaf":true,"timeout":20},"SERVER_NAME":"WebFE","SIGNIN_URL":"https://accounts.google.com/ServiceLogin?service\u003dyoutube\u0026uilel\u003d3\u0026passive\u003dtrue\u0026continue\u003dhttps%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26app%3Ddesktop%26hl%3Den%26next%3Dhttps%253A%252F%252Fwww.youtube.com%252Fwatch%253Fv%253DGJLlxj_dtq8%26feature%3D__FEATURE__\u0026hl\u003den","WEB_PLAYER_CONTEXT_CONFIGS":{"WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH":{"transparentBackground":true,"showMiniplayerButton":true,"externalFullscreen":true,"showMiniplayerUiWhenMinimized":true,"rootElementId":"movie_player","jsUrl":"/s/player/fc2a56a5/player_ias.vflset/en_US/base.js","cssUrl":"/s/player/fc2a56a5/www-player.css","contextId":"WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH","eventLabel":"detailpage","contentRegion":"DE","hl":"en_US","hostLanguage":"en","playerStyle":"desktop-polymer","innertubeApiKey":"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8","innertubeApiVersion":"v1","innertubeContextClientVersion":"2.20250610.00.00","device":{"brand":"robot","model":"bot or crawler","platform":"DESKTOP","interfaceName":"WEB","interfaceVersion":"2.20250610.00.00"},"serializedExperimentIds":"23986034,24004644,24439361,24566687,24699899,39325854,51010235,51063643,51072748,51091058,51095478,51098299,51204329,51222973,51237842,51285052,51300176,51300241,51313767,51314158,51318838,51340662,51342504,51349914,51353393,51354083,51355912,51366423,51389629,51397332,51404808,51404810,51409334,51425033,51430311,51432529,51439763,51439874,51444283,51447754,51456629,51459424,51462020,51462839,51463930,51467676,51470301,51471144,51471920,51472817,51475593,51476898,51477232,51478931,51479906,51481241,51481410,51481591,51481788,51483631,51484222,51486018,51488801,51489568,51490331,51490995,51492252,51492929,51493010,51494560,51494655,51496969,51497258,51498459,51500336,51505436,51508739,51509681,51509830,51509857,51510888,51511440,51513519,51516610","serializedExperimentFlags":"H5_async_logging_delay_ms\u003d30000.0\u0026H5_enable_full_pacf_logging\u003dtrue\u0026H5_use_async_logging\u003dtrue\u0026a11y_h5_associate_survey_question\u003dtrue\u0026ab_det_apb_b\u003dtrue\u0026ab_det_apm\u003dtrue\u0026ab_det_el_h\u003dtrue\u0026ab_det_em_inj\u003dtrue\u0026ab_l_sig_st\u003dtrue\u0026ab_l_sig_st_e\u003dtrue\u0026ab_sa_ef\u003dtrue\u0026action_companion_center_align_description\u003dtrue\u0026ad_pod_disable_companion_persist_ads_quality\u003dtrue\u0026align_three_dot_menu_with_title_description\u003dtrue\u0026allow_drm_override\u003dtrue\u0026allow_live_autoplay\u003dtrue\u0026allow_poltergust_autoplay\u003dtrue\u0026allow_proxima_live_latency\u003dtrue\u0026allow_skip_networkless\u003dtrue\u0026allow_vp9_1080p_mq_enc\u003dtrue\u0026always_cache_redirect_endpoint\u003dtrue\u0026assign_drm_family_by_format\u003dtrue\u0026att_web_record_metrics\u003dtrue\u0026attmusi\u003dtrue\u0026autoplay_time\u003d8000\u0026autoplay_time_for_fullscreen\u003d3000\u0026autoplay_time_for_music_content\u003d3000\u0026bg_st_hr\u003dtrue\u0026bg_vm_reinit_threshold\u003d7200000\u0026blocked_packages_for_sps\u003d[]\u0026botguard_async_snapshot_timeout_ms\u003d3000\u0026captions_url_add_ei\u003dtrue\u0026check_navigator_accuracy_timeout_ms\u003d0\u0026clean_player_style_fix_on_web\u003dtrue\u0026clean_up_manual_attribution_header\u003dtrue\u0026clear_user_partitioned_ls\u003dtrue\u0026client_respect_autoplay_switch_button_renderer\u003dtrue\u0026cobalt_h5vcc_h_t_t_p3\u003d0\u0026cobalt_h5vcc_media_dot_async_release_media_codec_bridge\u003d0\u0026cobalt_h5vcc_media_dot_audio_write_duration_local\u003d0\u0026cobalt_h5vcc_media_dot_player_configuration_dot_decode_to_texture_preferred\u003d0\u0026cobalt_h5vcc_media_dot_set_async_release_media_codec_bridge_timeout_seconds\u003d-1\u0026cobalt_h5vcc_media_element_dot_enable_using_media_source_attachment_methods\u003d0\u0026cobalt_h5vcc_media_element_dot_enable_using_media_source_buffered_range\u003d0\u0026cobalt_h5vcc_q_u_i_c\u003d0\u0026cobalt_h5vcc_set_prefer_minimal_post_processing\u003d0\u0026cobalt_h5vcc_string_q_u_i_c_connection_options\u003d\u0026compress_gel\u003dtrue\u0026compression_disable_point\u003d10\u0026cow_optimize_idom_compat\u003dtrue\u0026csi_config_handling_infra\u003dtrue\u0026csi_on_gel\u003dtrue\u0026custom_active_view_tos_timeout_ms\u003d3600000\u0026dash_manifest_version\u003d5\u0026debug_bandaid_hostname\u003d\u0026debug_sherlog_username\u003d\u0026delhi_modern_web_player_blending_mode\u003d\u0026deprecate_22\u003dtrue\u0026deprecate_csi_has_info\u003dtrue\u0026deprecate_delay_ping\u003dtrue\u0026deprecate_pair_servlet_enabled\u003dtrue\u0026desktop_sparkles_light_cta_button\u003dtrue\u0026disable_ad_duration_remaining_for_instream_video_ads\u003dtrue\u0026disable_ad_preview_for_instream_ads\u003dtrue\u0026disable_cached_masthead_data\u003dtrue\u0026disable_channel_id_check_for_suspended_channels\u003dtrue\u0026disable_child_node_auto_formatted_strings\u003dtrue\u0026disable_enf_isd\u003dtrue\u0026disable_features_for_supex\u003dtrue\u0026disable_legacy_desktop_remote_queue\u003dtrue\u0026disable_log_to_visitor_layer\u003dtrue\u0026disable_mdx_connection_in_mdx_module_for_music_web\u003dtrue\u0026disable_pacf_logging_for_memory_limited_tv\u003dtrue\u0026disable_reel_item_watch_format_filtering\u003dtrue\u0026disable_rounding_ad_notify\u003dtrue\u0026disable_ssdai_on_errors\u003dtrue\u0026disable_threegpp_progressive_formats\u003dtrue\u0026edge_encryption_fill_primary_key_version\u003dtrue\u0026embeds_disable_play_button_ve_focus_check\u003dtrue\u0026embeds_enable_autoplay_and_visibility_signals\u003dtrue\u0026embeds_enable_embedder_id_db_for_pfedu\u003dtrue\u0026embeds_enable_embedder_identity\u003dtrue\u0026embeds_enable_emc3ds_muted_autoplay\u003dtrue\u0026embeds_enable_emc3ds_playlist_buttons\u003dtrue\u0026embeds_enable_move_set_center_crop_to_public\u003dtrue\u0026embeds_enable_muted_autoplay_shorts_endscreen_fix\u003dtrue\u0026embeds_enable_pfp_always_unbranded\u003dtrue\u0026embeds_enable_rcat_embedder_identity\u003dtrue\u0026embeds_enable_vfsyb\u003dtrue\u0026embeds_focus_after_autohide_with_delay_ms\u003d1000\u0026embeds_web_enable_iframe_api_send_full_embed_url\u003dtrue\u0026embeds_web_enable_info_panel_sizing_fix\u003dtrue\u0026embeds_web_enable_load_player_from_page_show\u003dtrue\u0026embeds_web_enable_pause_overlay_rounding\u003dtrue\u0026embeds_web_enable_ve_conversion_logging_tracking_no_allow_list\u003dtrue\u0026embeds_web_lite_mode\u003d1\u0026embeds_web_nwl_disable_nocookie\u003dtrue\u0026embeds_web_synth_ch_headers_banned_urls_regex\u003d\u0026enable_ab_report_on_errorscreen\u003dtrue\u0026enable_ab_rp_int\u003dtrue\u0026enable_active_view_display_ad_renderer_web_home\u003dtrue\u0026enable_active_view_lifa_web_video\u003dtrue\u0026enable_active_view_lr_shorts_video\u003dtrue\u0026enable_active_view_web_shorts_video\u003dtrue\u0026enable_ad_context_in_vss_pings\u003dtrue\u0026enable_ad_cpn_macro_substitution_for_click_pings\u003dtrue\u0026enable_ad_pod_index_autohide\u003dtrue\u0026enable_app_promo_endcap_eml_on_tablet\u003dtrue\u0026enable_async_ab_enf\u003dtrue\u0026enable_av1_for_svod\u003dtrue\u0026enable_cairo_refresh_ringo2_web\u003dtrue\u0026enable_cast_for_web_unplugged\u003dtrue\u0026enable_cast_on_music_web\u003dtrue\u0026enable_cleanup_masthead_autoplay_hack_fix\u003dtrue\u0026enable_client_creator_goal_ticker_bar_revamp\u003dtrue\u0026enable_client_only_wiz_direct_reactions\u003dtrue\u0026enable_client_page_id_header_for_first_party_pings\u003dtrue\u0026enable_client_sli_logging\u003dtrue\u0026enable_client_ve_spec\u003dtrue\u0026enable_cow_info_csi\u003dtrue\u0026enable_cta_banner_on_unplugged_lr\u003dtrue\u0026enable_custom_playhead_parsing\u003dtrue\u0026enable_dai_sdf_h5_preroll\u003dtrue\u0026enable_dark_mode_style_endcap\u003dtrue\u0026enable_dark_mode_style_endcap_timed_pie_countdown\u003dtrue\u0026enable_datasync_id_header_in_web_vss_pings\u003dtrue\u0026enable_dsa_ad_badge_for_action_endcap_on_android\u003dtrue\u0026enable_dsa_ad_badge_for_action_endcap_on_ios\u003dtrue\u0026enable_dsa_ad_badge_for_action_endcap_on_web\u003dtrue\u0026enable_dsa_innertube_for_action_endcap_on_mobile\u003dtrue\u0026enable_dsa_innertube_for_action_endcap_on_web\u003dtrue\u0026enable_entity_store_from_dependency_injection\u003dtrue\u0026enable_error_corrections_infocard\u003dtrue\u0026enable_error_corrections_infocard_web_client\u003dtrue\u0026enable_error_corrections_infocard_web_client_check\u003dtrue\u0026enable_error_corrections_infocards_icon_web\u003dtrue\u0026enable_eviction_protection_for_bulleit\u003dtrue\u0026enable_flow_logging_p4e\u003dtrue\u0026enable_fully_reactive_badge_shape\u003dtrue\u0026enable_fully_reactive_button_shape\u003dtrue\u0026enable_gel_log_commands\u003dtrue\u0026enable_handles_account_menu_switcher\u003dtrue\u0026enable_is_extended_monitoring\u003dtrue\u0026enable_kabuki_comments_on_shorts\u003ddisabled\u0026enable_key_press_seek_logging\u003dtrue\u0026enable_lr_discovery_pings_ad_mt_macro\u003dtrue\u0026enable_lr_discovery_video_abandon_pings\u003dtrue\u0026enable_mixed_direction_formatted_strings\u003dtrue\u0026enable_modern_skip_button_on_web\u003dtrue\u0026enable_multiple_heatseeker_decorations\u003dtrue\u0026enable_mweb_livestream_ui_update\u003dtrue\u0026enable_new_paid_product_placement\u003dtrue\u0026enable_open_in_new_tab_icon_for_short_dr_for_desktop_search\u003dtrue\u0026enable_out_of_stock_text_all_surfaces\u003dtrue\u0026enable_pacf_slot_asde_infeed_h5\u003dtrue\u0026enable_pacf_slot_asde_player_byte_h5\u003dtrue\u0026enable_pacf_slot_asde_player_byte_h5_TV\u003dtrue\u0026enable_paid_content_overlay_bugfix\u003dtrue\u0026enable_persistent_device_token\u003dtrue\u0026enable_pl_r_c_s\u003dtrue\u0026enable_pl_r_si_fa\u003dtrue\u0026enable_populate_att_psd_in_abe_feedback\u003dtrue\u0026enable_populate_psd_in_abe_feedback\u003dtrue\u0026enable_progres_commands_lr_feeds\u003dtrue\u0026enable_pv_screen_modern_text\u003dtrue\u0026enable_qoe_reloaded_category\u003dtrue\u0026enable_redirect_linking_for_desktop_web_client\u003dtrue\u0026enable_rpr_token_on_ltl_lookup\u003dtrue\u0026enable_sabr_snackbar_message\u003dtrue\u0026enable_sdf_companion_h5\u003dtrue\u0026enable_sdf_dai_h5_midroll\u003dtrue\u0026enable_sdf_h5_endemic_mid_post_roll\u003dtrue\u0026enable_sdf_on_h5_unplugged_vod_midroll\u003dtrue\u0026enable_sdf_shorts_player_bytes_h5\u003dtrue\u0026enable_server_driven_abr\u003dtrue\u0026enable_server_driven_abr_for_backgroundable\u003dtrue\u0026enable_server_driven_abr_url_generation\u003dtrue\u0026enable_server_driven_readahead\u003dtrue\u0026enable_server_stitched_dai\u003dtrue\u0026enable_set_endcap_thumbnail_from_layout\u003dtrue\u0026enable_shorts_player\u003dtrue\u0026enable_skip_ad_guidance_prompt\u003dtrue\u0026enable_skip_to_next_messaging\u003dtrue\u0026enable_skippable_ads_for_unplugged_ad_pod\u003dtrue\u0026enable_smart_skip_player_controls_shown_on_web\u003dtrue\u0026enable_smart_skip_player_controls_shown_on_web_increased_triggering_sensitivity\u003dtrue\u0026enable_smart_skip_speedmaster_on_web\u003dtrue\u0026enable_smearing_expansion_dai\u003dtrue\u0026enable_split_screen_ad_baseline_experience_endemic_live_h5\u003dtrue\u0026enable_split_screen_ad_on_endemic_live_h5\u003dtrue\u0026enable_tectonic_ad_ux_for_halftime\u003dtrue\u0026enable_third_party_info\u003dtrue\u0026enable_topsoil_wta_for_halftime_live_infra\u003dtrue\u0026enable_unified_action_endcap_on_web\u003dtrue\u0026enable_video_display_compact_button_group_for_desktop_search\u003dtrue\u0026enable_voice_boost_feature\u003dtrue\u0026enable_vp9_appletv5_on_server\u003dtrue\u0026enable_web_96_bit_csn\u003dtrue\u0026enable_web_home_top_landscape_image_layout_level_click\u003dtrue\u0026enable_web_media_session_metadata_fix\u003dtrue\u0026enable_web_player_player_in_bar_feature\u003dtrue\u0026enable_web_tiered_gel\u003dtrue\u0026enable_wiz_always_try_logging_info_map\u003dtrue\u0026enable_wn_infocards\u003dtrue\u0026enable_write_dfss_metadata_to_ustreamer_config\u003dtrue\u0026enable_yt_ata_iframe_authuser\u003dtrue\u0026enable_ytv_csdai_vp9\u003dtrue\u0026err_on_pl_r_c\u003dtrue\u0026export_networkless_options\u003dtrue\u0026external_fullscreen_with_edu\u003dtrue\u0026fetch_att_independently\u003dtrue\u0026fill_live_request_config_in_ustreamer_config\u003dtrue\u0026fill_single_video_with_notify_to_lasr\u003dtrue\u0026filter_vb_without_non_vb_equivalents\u003dtrue\u0026fix_ads_tracking_for_swf_config_deprecation_mweb\u003dtrue\u0026fix_h5_toggle_button_a11y\u003dtrue\u0026fix_survey_color_contrast_on_destop\u003dtrue\u0026fix_toggle_button_role_for_ad_components\u003dtrue\u0026fix_web_instream_survey_question_aria_label\u003dtrue\u0026fresca_polling_delay_override\u003d0\u0026gab_return_sabr_ssdai_config\u003dtrue\u0026gel_min_batch_size\u003d3\u0026gel_queue_timeout_max_ms\u003d300000\u0026gvi_channel_client_screen\u003dtrue\u0026h5_companion_enable_adcpn_macro_substitution_for_click_pings\u003dtrue\u0026h5_enable_ad_mbs\u003dtrue\u0026h5_inplayer_enable_adcpn_macro_substitution_for_click_pings\u003dtrue\u0026h5_reset_cache_and_filter_before_update_masthead\u003dtrue\u0026heatseeker_decoration_threshold\u003d0.8\u0026hfr_dropped_framerate_fallback_threshold\u003d0\u0026hide_cta_for_home_web_video_ads_animate_in_time\u003d2\u0026hide_endpoint_overflow_on_ytd_display_ad_renderer\u003dtrue\u0026hls_use_new_codecs_string_api\u003dtrue\u0026html5_account_for_underrun_advance_in_sabr\u003dtrue\u0026html5_ad_timeout_ms\u003d0\u0026html5_adaptation_step_count\u003d0\u0026html5_ads_preroll_lock_timeout_delay_ms\u003d15000\u0026html5_allow_preloading_with_idle_only_network_for_sabr\u003dtrue\u0026html5_allow_video_keyframe_without_audio\u003dtrue\u0026html5_apply_constraints_in_client_for_sabr\u003dtrue\u0026html5_apply_min_failures\u003dtrue\u0026html5_apply_start_time_within_ads_for_ssdai_transitions\u003dtrue\u0026html5_atr_disable_force_fallback\u003dtrue\u0026html5_attach_num_random_bytes_to_bandaid\u003d0\u0026html5_attach_po_token_to_bandaid\u003dtrue\u0026html5_autonav_cap_idle_secs\u003d0\u0026html5_autonav_quality_cap\u003d720\u0026html5_autoplay_default_quality_cap\u003d0\u0026html5_auxiliary_estimate_weight\u003d0.0\u0026html5_av1_ordinal_cap\u003d0\u0026html5_block_onesie_hqa_fmt_by_default\u003dtrue\u0026html5_block_pip_safari_delay\u003d0\u0026html5_bypass_contention_secs\u003d0.0\u0026html5_byterate_soft_cap\u003d0\u0026html5_check_for_idle_network_interval_ms\u003d1000\u0026html5_check_video_data_errors_before_playback_start\u003dtrue\u0026html5_chipset_soft_cap\u003d8192\u0026html5_cobalt_audio_write_ahead_ms\u003d0\u0026html5_cobalt_override_quic\u003d0\u0026html5_consume_all_buffered_bytes_one_poll\u003dtrue\u0026html5_continuous_goodput_probe_interval_ms\u003d0\u0026html5_d6de4_cloud_project_number\u003d868618676952\u0026html5_d6de4_defer_timeout_ms\u003d0\u0026html5_debug_data_log_probability\u003d0.0\u0026html5_decode_to_texture_cap\u003dtrue\u0026html5_default_ad_gain\u003d0.5\u0026html5_default_audio_quality_setting_lr\u003d0\u0026html5_default_av1_threshold\u003d0\u0026html5_default_quality_cap\u003d0\u0026html5_defer_fetch_att_ms\u003d1000\u0026html5_delayed_retry_count\u003d1\u0026html5_delayed_retry_delay_ms\u003d5000\u0026html5_deprecate_adservice\u003dtrue\u0026html5_deprecate_manifestful_fallback\u003dtrue\u0026html5_deprecate_video_tag_pool\u003dtrue\u0026html5_desktop_vr180_allow_panning\u003dtrue\u0026html5_df_downgrade_thresh\u003d0.6\u0026html5_disable_av1_arm_check\u003dtrue\u0026html5_disable_bandwidth_cofactors_for_sabr_live\u003dtrue\u0026html5_disable_client_autonav_cap_for_onesie\u003dtrue\u0026html5_disable_live_dvr_shrink_for_cdm_vss\u003dtrue\u0026html5_disable_move_pssh_to_moov\u003dtrue\u0026html5_disable_non_contiguous\u003dtrue\u0026html5_disable_peak_shave_for_onesie\u003dtrue\u0026html5_disable_snackbar_message_checking_on_seeking_to_play\u003dtrue\u0026html5_disable_ustreamer_constraint_for_sabr\u003dtrue\u0026html5_disable_web_safari_dai\u003dtrue\u0026html5_dispatch_tracklist_loaded_event\u003dtrue\u0026html5_displayed_frame_rate_downgrade_threshold\u003d45\u0026html5_dispose_modules_in_order\u003dtrue\u0026html5_drm_byterate_soft_cap\u003d0\u0026html5_drm_check_all_key_error_states\u003dtrue\u0026html5_drm_cpi_license_key\u003dtrue\u0026html5_drm_live_byterate_soft_cap\u003d0\u0026html5_early_media_for_sharper_shorts\u003dtrue\u0026html5_embeds_fix_itct\u003dtrue\u0026html5_enable_ac3\u003dtrue\u0026html5_enable_audio_track_stickiness\u003dtrue\u0026html5_enable_audio_track_stickiness_phase_two\u003dtrue\u0026html5_enable_bandaid_error_screen\u003dtrue\u0026html5_enable_caption_changes_for_mosaic\u003dtrue\u0026html5_enable_composite_embargo\u003dtrue\u0026html5_enable_d6de4\u003dtrue\u0026html5_enable_d6de4_cold_start_and_error\u003dtrue\u0026html5_enable_d6de4_idle_priority_job\u003dtrue\u0026html5_enable_drc\u003dtrue\u0026html5_enable_drc_toggle_api\u003dtrue\u0026html5_enable_eac3\u003dtrue\u0026html5_enable_embedded_player_visibility_signals\u003dtrue\u0026html5_enable_encrypted_av1\u003dtrue\u0026html5_enable_media_serving_enforcement\u003dtrue\u0026html5_enable_media_serving_enforcement_when_has_preroll_ad\u003dtrue\u0026html5_enable_oduc\u003dtrue\u0026html5_enable_onesie_media_for_sabr_proxima_optin\u003dtrue\u0026html5_enable_po_token_live_caption\u003dtrue\u0026html5_enable_progress_bar_slide_seek_logging\u003dtrue\u0026html5_enable_sabr_context_from_ump\u003dtrue\u0026html5_enable_sabr_drm_vod_streaming_xhr\u003dtrue\u0026html5_enable_sabr_format_selection\u003dtrue\u0026html5_enable_sabr_from_watch_server\u003dtrue\u0026html5_enable_sabr_host_fallback\u003dtrue\u0026html5_enable_sabr_ssdai_streaming_xhr\u003dtrue\u0026html5_enable_sabr_vod_streaming_xhr\u003dtrue\u0026html5_enable_server_driven_request_cancellation\u003dtrue\u0026html5_enable_sps_non_fatal_logs\u003dtrue\u0026html5_enable_sps_rebuffer_logs\u003dtrue\u0026html5_enable_sps_retry_backoff_metadata_requests\u003dtrue\u0026html5_enable_sps_retry_backoff_ms\u003d2000\u0026html5_enable_tvos_dash\u003dtrue\u0026html5_enable_tvos_encrypted_vp9\u003dtrue\u0026html5_enable_widevine_for_alc\u003dtrue\u0026html5_enable_widevine_for_fast_linear\u003dtrue\u0026html5_encourage_array_coalescing\u003dtrue\u0026html5_entity_id_simplified_preferred_record_size\u003d50\u0026html5_fix_multi_audio_offline_playback\u003dtrue\u0026html5_fixed_media_duration_for_request\u003d0\u0026html5_flush_index_on_updated_timestamp_offset\u003dtrue\u0026html5_force_sabr_from_watch_server_for_dfss\u003dtrue\u0026html5_future_encryptor_auto_select_js_max_size_bytes\u003d4096\u0026html5_future_encryptor_auto_select_wasm_max_size_bytes\u003d32768\u0026html5_gapless_ended_transition_buffer_ms\u003d200\u0026html5_gapless_handoff_close_end_long_rebuffer_cfl\u003dtrue\u0026html5_gapless_handoff_close_end_long_rebuffer_delay_ms\u003d0\u0026html5_gapless_loop_seek_offset_in_milli\u003d0\u0026html5_gapless_seek_offset\u003d0.0\u0026html5_gapless_slice_append_stuck_cfl\u003dtrue\u0026html5_gapless_slice_append_stuck_delay_ms\u003d0\u0026html5_gapless_slow_seek_cfl\u003dtrue\u0026html5_gapless_slow_seek_delay_ms\u003d0\u0026html5_gapless_slow_start_delay_ms\u003d0\u0026html5_generate_session_po_token\u003dtrue\u0026html5_gl_fps_threshold\u003d0\u0026html5_handle_sps_status\u003dtrue\u0026html5_hdcp_probing_stream_url\u003d\u0026html5_head_miss_secs\u003d0.0\u0026html5_hfr_quality_cap\u003d0\u0026html5_high_res_logging_percent\u003d0.01\u0026html5_high_res_seek_logging\u003dtrue\u0026html5_honor_caption_availabilities_in_audio_track\u003dtrue\u0026html5_hopeless_secs\u003d0\u0026html5_idle_rate_limit_ms\u003d0\u0026html5_ignore_partial_segment_from_live_readahead\u003dtrue\u0026html5_innertube_heartbeats_for_fairplay\u003dtrue\u0026html5_innertube_heartbeats_for_playready\u003dtrue\u0026html5_innertube_heartbeats_for_widevine\u003dtrue\u0026html5_ios4_seek_above_zero\u003dtrue\u0026html5_ios7_force_play_on_stall\u003dtrue\u0026html5_ios_force_seek_to_zero_on_stop\u003dtrue\u0026html5_js_self_profiler_max_samples\u003d0\u0026html5_js_self_profiler_sample_interval_ms\u003d0\u0026html5_jumbo_mobile_subsegment_readahead_target\u003d3.0\u0026html5_jumbo_ull_nonstreaming_mffa_ms\u003d4000\u0026html5_jumbo_ull_subsegment_readahead_target\u003d1.3\u0026html5_kabuki_drm_live_51_default_off\u003dtrue\u0026html5_kaios_max_resolution\u003d0\u0026html5_license_constraint_delay\u003d5000\u0026html5_lifa_ignore_multiple_skips\u003dtrue\u0026html5_lifa_overdecorate_fix\u003dtrue\u0026html5_lifa_skip_to_content\u003dtrue\u0026html5_live_abr_head_miss_fraction\u003d0.0\u0026html5_live_abr_repredict_fraction\u003d0.0\u0026html5_live_chunk_readahead_proxima_override\u003d0\u0026html5_live_head_playable\u003dtrue\u0026html5_live_low_latency_bandwidth_window\u003d0.0\u0026html5_live_normal_latency_bandwidth_window\u003d0.0\u0026html5_live_quality_cap\u003d0\u0026html5_live_ultra_low_latency_bandwidth_window\u003d0.0\u0026html5_livebadge_color_update\u003dtrue\u0026html5_liveness_drift_chunk_override\u003d0\u0026html5_liveness_drift_proxima_override\u003d0\u0026html5_log_audio_abr\u003dtrue\u0026html5_log_audio_switching_latency\u003dtrue\u0026html5_log_experiment_id_from_player_response_to_ctmp\u003d\u0026html5_log_first_ssdai_requests_killswitch\u003dtrue\u0026html5_log_rebuffer_events\u003d5\u0026html5_log_trigger_events_with_debug_data\u003dtrue\u0026html5_log_vss_extra_lr_cparams_freq\u003d\u0026html5_long_rebuffer_jiggle_cmt_delay_ms\u003d0\u0026html5_long_rebuffer_ssap_clip_not_match_delay_ms\u003d0\u0026html5_long_rebuffer_threshold_ms\u003d30000\u0026html5_low_latency_adaptive_liveness_adjustment_segments\u003d0\u0026html5_low_latency_max_allowable_liveness_drift_chunks\u003d0\u0026html5_manifestless_unplugged\u003dtrue\u0026html5_manifestless_vp9_otf\u003dtrue\u0026html5_max_buffer_health_for_downgrade_prop\u003d0.0\u0026html5_max_buffer_health_for_downgrade_secs\u003d0.0\u0026html5_max_byterate\u003d0\u0026html5_max_discontinuity_rewrite_count\u003d0\u0026html5_max_drift_per_track_secs\u003d0.0\u0026html5_max_headm_for_streaming_xhr\u003d0\u0026html5_max_live_dvr_window_plus_margin_secs\u003d46800.0\u0026html5_max_quality_sel_upgrade\u003d0\u0026html5_max_redirect_response_length\u003d8192\u0026html5_max_selectable_quality_ordinal\u003d0\u0026html5_max_vertical_resolution\u003d0\u0026html5_maximum_readahead_seconds\u003d0.0\u0026html5_media_fullscreen\u003dtrue\u0026html5_media_time_weight\u003dtrue\u0026html5_media_time_weight_prop\u003d0.0\u0026html5_mffa_ms_proxima_override\u003d0\u0026html5_min_failures_to_delay_retry\u003d3\u0026html5_min_media_duration_for_append_prop\u003d0.0\u0026html5_min_media_duration_for_cabr_slice\u003d0.01\u0026html5_min_playback_advance_for_steady_state_secs\u003d0\u0026html5_min_quality_ordinal\u003d0\u0026html5_min_readbehind_cap_secs\u003d60\u0026html5_min_readbehind_secs\u003d0\u0026html5_min_seconds_between_format_selections\u003d0.0\u0026html5_min_selectable_quality_ordinal\u003d0\u0026html5_min_startup_buffered_media_duration_for_live_secs\u003d0.0\u0026html5_min_startup_buffered_media_duration_secs\u003d1.2\u0026html5_min_startup_duration_live_secs\u003d0.25\u0026html5_min_underrun_buffered_pre_steady_state_ms\u003d0\u0026html5_min_upgrade_health_secs\u003d0.0\u0026html5_minimum_readahead_seconds\u003d0.0\u0026html5_mock_content_binding_for_session_token\u003d\u0026html5_move_disable_airplay\u003dtrue\u0026html5_new_wpo_client\u003dtrue\u0026html5_no_csi_on_replay\u003dtrue\u0026html5_no_placeholder_rollbacks\u003dtrue\u0026html5_non_onesie_attach_po_token\u003dtrue\u0026html5_normal_latency_mffa_ms\u003d0\u0026html5_oduc_transfer_logging\u003dtrue\u0026html5_offline_download_timeout_retry_limit\u003d4\u0026html5_offline_failure_retry_limit\u003d2\u0026html5_offline_playback_position_sync\u003dtrue\u0026html5_offline_prevent_redownload_downloaded_video\u003dtrue\u0026html5_onesie_audio_only_playback\u003dtrue\u0026html5_onesie_check_timeout\u003dtrue\u0026html5_onesie_defer_content_loader_ms\u003d0\u0026html5_onesie_live_ttl_secs\u003d8\u0026html5_onesie_prewarm_interval_ms\u003d0\u0026html5_onesie_prewarm_max_lact_ms\u003d0\u0026html5_onesie_redirector_timeout_ms\u003d0\u0026html5_onesie_request_timeout_ms\u003d1000\u0026html5_onesie_send_streamer_context\u003dtrue\u0026html5_onesie_use_signed_onesie_ustreamer_config\u003dtrue\u0026html5_override_micro_discontinuities_threshold_ms\u003d-1\u0026html5_override_oversend_fraction\u003d0.0\u0026html5_paced_poll_min_health_ms\u003d0\u0026html5_paced_poll_ms\u003d0\u0026html5_pause_on_nonforeground_platform_errors\u003dtrue\u0026html5_peak_shave\u003dtrue\u0026html5_perf_cap_override_sticky\u003dtrue\u0026html5_performance_cap_floor\u003d360\u0026html5_performance_impact_profiling_timer_ms\u003d0\u0026html5_perserve_av1_perf_cap\u003dtrue\u0026html5_picture_in_picture_logging_onresize_ratio\u003d0.0\u0026html5_platform_max_buffer_health_oversend_duration_secs\u003d10.0\u0026html5_platform_minimum_readahead_seconds\u003d0.0\u0026html5_player_att_initial_delay_ms\u003d1000\u0026html5_player_att_retry_delay_ms\u003d1500\u0026html5_player_autonav_logging\u003dtrue\u0026html5_player_dynamic_bottom_gradient\u003dtrue\u0026html5_player_min_build_cl\u003d-1\u0026html5_player_preload_ad_fix\u003dtrue\u0026html5_post_interrupt_readahead\u003d20\u0026html5_prefer_server_bwe3\u003dtrue\u0026html5_preload_before_initial_seek_with_sabr\u003dtrue\u0026html5_preload_wait_time_secs\u003d0.0\u0026html5_probe_primary_delay_base_ms\u003d0\u0026html5_process_all_encrypted_events\u003dtrue\u0026html5_profiler_trace_enums\u003d[]\u0026html5_progress_event_throttle_ms\u003d0\u0026html5_qoe_proto_mock_length\u003d0\u0026html5_query_sw_secure_crypto_for_android\u003dtrue\u0026html5_random_playback_cap\u003d0\u0026html5_recognize_predict_start_cue_point\u003dtrue\u0026html5_record_ump_timing\u003dtrue\u0026html5_remove_command_triggered_companions\u003dtrue\u0026html5_remove_not_servable_check_killswitch\u003dtrue\u0026html5_remove_ssdai_append_pause\u003dtrue\u0026html5_rename_apbs\u003dtrue\u0026html5_report_fatal_drm_restricted_error_killswitch\u003dtrue\u0026html5_report_max_buffer_bytes_limit_to_sabr\u003dtrue\u0026html5_report_slow_ads_as_error\u003dtrue\u0026html5_report_supports_vp9_encoding\u003dtrue\u0026html5_repredict_interval_ms\u003d0\u0026html5_request_only_hdr_or_sdr_keys\u003dtrue\u0026html5_request_size_max_kb\u003d0\u0026html5_request_size_min_kb\u003d0\u0026html5_reseek_after_time_jump_cfl\u003dtrue\u0026html5_reseek_after_time_jump_delay_ms\u003d0\u0026html5_resource_bad_status_delay_scaling\u003d1.5\u0026html5_restrict_streaming_xhr_on_sqless_requests\u003dtrue\u0026html5_retry_downloads_for_expiration\u003dtrue\u0026html5_retry_on_drm_key_error\u003dtrue\u0026html5_retry_on_drm_unavailable\u003dtrue\u0026html5_retry_quota_exceeded_via_seek\u003dtrue\u0026html5_sabr_enable_live_clock_offset\u003dtrue\u0026html5_sabr_enable_server_xtag_selection\u003dtrue\u0026html5_sabr_fail_request_after_handle_available_slices\u003dtrue\u0026html5_sabr_fetch_on_idle_network_preloaded_players\u003dtrue\u0026html5_sabr_force_max_network_interruption_duration_ms\u003d0\u0026html5_sabr_jiggle_cmt_ms\u003d1\u0026html5_sabr_live_drm_streaming_xhr\u003dtrue\u0026html5_sabr_live_normal_latency_streaming_xhr\u003dtrue\u0026html5_sabr_live_timing\u003dtrue\u0026html5_sabr_log_server_xtag_selection_onesie_mismatch\u003dtrue\u0026html5_sabr_malformed_config_retry_limit\u003d0\u0026html5_sabr_min_media_bytes_factor_to_append_for_stream\u003d0.0\u0026html5_sabr_report_missing_url_as_error_terminal\u003dtrue\u0026html5_sabr_report_partial_segment_estimated_duration\u003dtrue\u0026html5_sabr_report_request_cancellation_info\u003dtrue\u0026html5_sabr_request_limit_per_period\u003d20\u0026html5_sabr_seek_epsilon_ms\u003d0\u0026html5_sabr_seek_no_shift_tolerance\u003dtrue\u0026html5_sabr_skip_client_audio_init_selection\u003dtrue\u0026html5_sabr_timeout_penalty_factor\u003d0.0\u0026html5_safari_desktop_eme_min_version\u003d0\u0026html5_samsung_kant_limit_max_bitrate\u003d0\u0026html5_seek_jiggle_cmt_delay_ms\u003d8000\u0026html5_seek_new_elem_delay_ms\u003d12000\u0026html5_seek_new_elem_shorts_delay_ms\u003d2000\u0026html5_seek_new_media_element_shorts_reuse_cfl\u003dtrue\u0026html5_seek_new_media_element_shorts_reuse_delay_ms\u003d0\u0026html5_seek_new_media_source_shorts_reuse_cfl\u003dtrue\u0026html5_seek_new_media_source_shorts_reuse_delay_ms\u003d0\u0026html5_seek_set_cmt_delay_ms\u003d2000\u0026html5_seek_timeout_delay_ms\u003d20000\u0026html5_serve_start_seconds_seek_for_post_live_sabr\u003dtrue\u0026html5_server_stitched_dai_decorated_url_retry_limit\u003d5\u0026html5_server_stitched_dai_group\u003dtrue\u0026html5_session_po_token_interval_time_ms\u003d900000\u0026html5_shorts_gapless_ad_slow_start_delay_ms\u003d0\u0026html5_shorts_gapless_next_buffer_in_seconds\u003d0\u0026html5_show_drc_toggle\u003dtrue\u0026html5_simplified_backup_timeout_sabr_live\u003dtrue\u0026html5_skip_empty_po_token\u003dtrue\u0026html5_skip_slow_ad_delay_ms\u003d15000\u0026html5_slow_start_no_media_source_delay_ms\u003d0\u0026html5_slow_start_timeout_delay_ms\u003d20000\u0026html5_ssap_ad_longrebuffer_new_element_delay_ms\u003d0\u0026html5_ssap_fix_ad_completion_cue_range\u003dtrue\u0026html5_ssap_ignore_initial_seek_if_too_big\u003dtrue\u0026html5_ssap_segment_end_media_threshold_ms\u003d200\u0026html5_ssap_set_format_info_for_all\u003dtrue\u0026html5_ssap_skip_seeking_offset_ms\u003d0\u0026html5_ssdai_always_publish_ad_state_change\u003dtrue\u0026html5_ssdai_disable_seek_to_skip\u003dtrue\u0026html5_ssdai_enable_new_seek_logic\u003dtrue\u0026html5_ssdai_failure_retry_limit\u003d0\u0026html5_stall_factor\u003d0.0\u0026html5_sticky_duration_mos\u003d0\u0026html5_store_xhr_headers_readable\u003dtrue\u0026html5_streaming_resilience\u003dtrue\u0026html5_streaming_xhr_time_based_consolidation_ms\u003d-1\u0026html5_subsegment_readahead_load_speed_check_interval\u003d0.5\u0026html5_subsegment_readahead_min_buffer_health_secs\u003d0.25\u0026html5_subsegment_readahead_min_buffer_health_secs_on_timeout\u003d0.1\u0026html5_subsegment_readahead_min_load_speed\u003d1.5\u0026html5_subsegment_readahead_seek_latency_fudge\u003d0.5\u0026html5_subsegment_readahead_target_buffer_health_secs\u003d0.5\u0026html5_subsegment_readahead_timeout_secs\u003d2.0\u0026html5_track_overshoot\u003dtrue\u0026html5_transfer_processing_logs_interval\u003d1000\u0026html5_trigger_loader_when_idle_network\u003dtrue\u0026html5_ugc_live_audio_51\u003dtrue\u0026html5_ugc_vod_audio_51\u003dtrue\u0026html5_unplugged_load_test_regions\u003d[]\u0026html5_unreported_seek_reseek_delay_ms\u003d0\u0026html5_update_time_on_seeked\u003dtrue\u0026html5_use_buffer_timeline_for_sabr_request_creation\u003dtrue\u0026html5_use_date_now_for_local_storage\u003dtrue\u0026html5_use_init_selected_audio\u003dtrue\u0026html5_use_jsonformatter_to_parse_player_response\u003dtrue\u0026html5_use_non_active_broadcast_for_post_live\u003dtrue\u0026html5_use_post_for_media\u003dtrue\u0026html5_use_server_qoe_el_value\u003dtrue\u0026html5_use_shared_owl_instance\u003dtrue\u0026html5_use_ump\u003dtrue\u0026html5_use_ump_request_slicer\u003dtrue\u0026html5_use_ump_timing\u003dtrue\u0026html5_use_video_quality_cap_for_ustreamer_constraint\u003dtrue\u0026html5_use_video_transition_endpoint_heartbeat\u003dtrue\u0026html5_use_webfe_ouc_refactor\u003dtrue\u0026html5_video_tbd_min_kb\u003d0\u0026html5_viewport_undersend_maximum\u003d0.0\u0026html5_volume_slider_tooltip\u003dtrue\u0026html5_web_po_experiment_ids\u003d[]\u0026html5_web_po_request_key\u003dHVXUKVy98PAjvWs7DkU0\u0026html5_web_po_send_onevent_ticks\u003dtrue\u0026html5_webpo_idle_priority_job\u003dtrue\u0026html5_webpo_kaios_defer_timeout_ms\u003d0\u0026html5_woffle_resume\u003dtrue\u0026html5_workaround_delay_trigger\u003dtrue\u0026ignore_overlapping_cue_points_on_endemic_live_html5\u003dtrue\u0026il_attach_cache_limit\u003dtrue\u0026il_payload_scraping\u003d\u0026il_use_view_model_logging_context\u003dtrue\u0026increase_completion_ping_firing_window\u003dtrue\u0026initial_gel_batch_timeout\u003d2000\u0026injected_license_handler_error_code\u003d0\u0026injected_license_handler_license_status\u003d0\u0026ios_and_android_fresca_polling_delay_override\u003d0\u0026itdrm_always_generate_media_keys\u003dtrue\u0026itdrm_always_use_widevine_sdk\u003dtrue\u0026itdrm_disable_external_key_rotation_system_ids\u003d[]\u0026itdrm_enable_revocation_reporting\u003dtrue\u0026itdrm_injected_license_service_error_code\u003d0\u0026itdrm_use_widevine_sdk_for_premium_content\u003dtrue\u0026itdrm_use_widevine_sdk_only_for_sampled_dod\u003dtrue\u0026itdrm_widevine_hardened_vmp_mode\u003dlog\u0026json_condensed_response\u003dtrue\u0026kev_adb_pg\u003dtrue\u0026kevlar_command_handler_command_banlist\u003d[]\u0026kevlar_delhi_modern_web_endscreen_ideal_tile_width_percentage\u003d0.27\u0026kevlar_delhi_modern_web_endscreen_max_rows\u003d2\u0026kevlar_delhi_modern_web_endscreen_max_width\u003d500\u0026kevlar_delhi_modern_web_endscreen_min_width\u003d200\u0026kevlar_gel_error_routing\u003dtrue\u0026kevlar_miniplayer_expand_top\u003dtrue\u0026kevlar_miniplayer_play_pause_on_scrim\u003dtrue\u0026kevlar_playback_associated_queue\u003dtrue\u0026kevlar_use_wil_icons\u003dtrue\u0026kevlar_vimio_use_shared_monitor\u003dtrue\u0026launch_license_service_all_ott_videos_automatic_fail_open\u003dtrue\u0026live_chat_enable_controller_extraction\u003dtrue\u0026live_chat_enable_pagehide_listener\u003dtrue\u0026live_chat_enable_rta_manager\u003dtrue\u0026live_chunk_readahead\u003d3\u0026log_click_with_layer_from_element_in_command_handler\u003dtrue\u0026log_errors_through_nwl_on_retry\u003dtrue\u0026log_gel_compression_latency\u003dtrue\u0026log_heartbeat_with_lifecycles\u003dtrue\u0026log_window_onerror_fraction\u003d0.1\u0026main_web_redirect_integration_riot\u003dtrue\u0026manifestless_post_live\u003dtrue\u0026manifestless_post_live_ufph\u003dtrue\u0026max_body_size_to_compress\u003d500000\u0026max_cdfe_quality_ordinal\u003d0\u0026max_prefetch_window_sec_for_livestream_optimization\u003d10\u0026max_resolution_for_white_noise\u003d360\u0026mdx_enable_privacy_disclosure_ui\u003dtrue\u0026mdx_load_cast_api_bootstrap_script\u003dtrue\u0026migrate_events_to_ts\u003dtrue\u0026migrate_remaining_web_ad_badges_to_innertube\u003dtrue\u0026min_prefetch_offset_sec_for_livestream_optimization\u003d20\u0026mta_drc_mutual_exclusion_removal\u003dtrue\u0026music_enable_shared_audio_tier_logic\u003dtrue\u0026mweb_account_linking_noapp\u003dtrue\u0026mweb_c3_endscreen\u003dtrue\u0026mweb_deprecate_skip_ve_logging\u003dtrue\u0026mweb_enable_skippables_on_jio_phone\u003dtrue\u0026mweb_native_control_in_faux_fullscreen_shared\u003dtrue\u0026mweb_shorts_comments_panel_id_change\u003dtrue\u0026network_polling_interval\u003d30000\u0026networkless_gel\u003dtrue\u0026networkless_logging\u003dtrue\u0026new_codecs_string_api_uses_legacy_style\u003dtrue\u0026new_csn_storage_design\u003dtrue\u0026no_drm_on_demand_with_cc_license\u003dtrue\u0026no_filler_video_for_ssa_playbacks\u003dtrue\u0026nwl_send_fast_on_unload\u003dtrue\u0026nwl_send_from_memory_when_online\u003dtrue\u0026offline_error_handling\u003dtrue\u0026onesie_add_gfe_frontline_to_player_request\u003dtrue\u0026onesie_enable_override_headm\u003dtrue\u0026override_drm_required_playback_policy_channels\u003d[]\u0026pageid_as_header_web\u003dtrue\u0026player_ads_set_adformat_on_client\u003dtrue\u0026player_bootstrap_method\u003dtrue\u0026player_destroy_old_version\u003dtrue\u0026player_doubletap_to_seek\u003dtrue\u0026player_enable_playback_playlist_change\u003dtrue\u0026player_underlay_min_player_width\u003d768.0\u0026player_underlay_video_width_fraction\u003d0.6\u0026player_web_canary_stage\u003d0\u0026playready_first_play_expiration\u003d-1\u0026podcasts_videostats_default_flush_interval_seconds\u003d0\u0026polymer_bad_build_labels\u003dtrue\u0026polymer_verifiy_app_state\u003dtrue\u0026populate_format_set_info_in_cdfe_formats\u003dtrue\u0026populate_head_minus_in_watch_server\u003dtrue\u0026preskip_button_style_ads_backend\u003dcountdown_next_to_thumbnail\u0026proxima_auto_threshold_max_network_interruption_duration_ms\u003d0\u0026proxima_auto_threshold_min_bandwidth_estimate_bytes_per_sec\u003d0\u0026qoe_nwl_downloads\u003dtrue\u0026qoe_send_and_write\u003dtrue\u0026read_data_from_web_component_wrapper\u003dtrue\u0026record_app_crashed_web\u003dtrue\u0026refactor_mta_default_track_selection\u003dtrue\u0026reject_hidden_live_formats\u003dtrue\u0026reject_live_vp9_mq_clear_with_no_abr_ladder\u003dtrue\u0026remove_masthead_channel_banner_on_refresh\u003dtrue\u0026remove_slot_id_exited_trigger_for_dai_in_player_slot_expire\u003dtrue\u0026replace_client_url_parsing_with_server_signal\u003dtrue\u0026replace_playability_retriever_in_watch\u003dtrue\u0026return_drm_product_unknown_for_clear_playbacks\u003dtrue\u0026sabr_enable_host_fallback\u003dtrue\u0026scheduler_use_raf_by_default\u003dtrue\u0026self_podding_header_string_template\u003dself_podding_interstitial_message\u0026self_podding_highlight_non_default_button\u003dtrue\u0026self_podding_midroll_choice_string_template\u003dself_podding_midroll_choice\u0026send_config_hash_timer\u003d0\u0026serve_adaptive_fmts_for_live_streams\u003dtrue\u0026set_interstitial_advertisers_question_text\u003dtrue\u0026set_mock_id_as_expected_content_binding\u003d\u0026shared_enable_controller_extraction\u003dtrue\u0026shared_enable_sink_wrapping\u003dtrue\u0026shell_load_gcf\u003dtrue\u0026short_start_time_prefer_publish_in_watch_log\u003dtrue\u0026shorts_mode_to_player_api\u003dtrue\u0026should_clear_video_data_on_player_cued_unstarted\u003dtrue\u0026show_preskip_progress_bar_for_skippable_ads\u003dtrue\u0026simply_embedded_enable_botguard\u003dtrue\u0026skip_inline_muted_license_service_check\u003dtrue\u0026skip_invalid_ytcsi_ticks\u003dtrue\u0026skip_ls_gel_retry\u003dtrue\u0026skip_setting_info_in_csi_data_object\u003dtrue\u0026slow_compressions_before_abandon_count\u003d4\u0026smarter_ve_dedupping\u003dtrue\u0026speedmaster_cancellation_movement_dp\u003d10\u0026speedmaster_playback_rate\u003d2.0\u0026speedmaster_touch_activation_ms\u003d500\u0026sps_enable_antiscraping_rules\u003dtrue\u0026start_client_gcf\u003dtrue\u0026streaming_data_emergency_itag_blacklist\u003d[]\u0026substitute_ad_cpn_macro_in_ssdai\u003dtrue\u0026suppress_error_204_logging\u003dtrue\u0026swatcheroo_pbs_max_delay_ms\u003d3000\u0026transport_use_scheduler\u003dtrue\u0026trim_adaptive_formats_signature_cipher_for_sabr_content\u003dtrue\u0026trim_adaptive_formats_url_for_media_barrier\u003dtrue\u0026tv_pacf_logging_sample_rate\u003d0.01\u0026tvhtml5_unplugged_preload_cache_size\u003d5\u0026unplugged_tvhtml5_video_preload_on_focus_delay_ms\u003d0\u0026use_color_palettes_modern_collections_v2\u003dtrue\u0026use_core_sm\u003dtrue\u0026use_csi_stp_handler\u003dtrue\u0026use_event_time_ms_header\u003dtrue\u0026use_fifo_for_networkless\u003dtrue\u0026use_generated_media_keys_in_fairplay_requests\u003dtrue\u0026use_infogel_early_logging\u003dtrue\u0026use_inlined_player_rpc\u003dtrue\u0026use_new_codecs_string_api\u003dtrue\u0026use_new_in_memory_storage\u003dtrue\u0026use_player_abuse_bg_library\u003dtrue\u0026use_request_time_ms_header\u003dtrue\u0026use_rta_for_player\u003dtrue\u0026use_rta_for_player_hb\u003dtrue\u0026use_rta_only_for_player\u003dtrue\u0026use_session_based_sampling\u003dtrue\u0026use_simplified_remove_webm_rules\u003dtrue\u0026use_ts_visibilitylogger\u003dtrue\u0026use_video_playback_premium_signal\u003dtrue\u0026variable_buffer_timeout_ms\u003d0\u0026vp9_drm_live\u003dtrue\u0026vss_final_ping_send_and_write\u003dtrue\u0026vss_pings_using_networkless\u003dtrue\u0026vss_playback_use_send_and_write\u003dtrue\u0026web_api_url\u003dtrue\u0026web_big_boards\u003dtrue\u0026web_big_boards_enable_in_inline\u003dtrue\u0026web_big_boards_enable_in_miniplayer\u003dtrue\u0026web_cairo_modern_miniplayer\u003dtrue\u0026web_cairo_modern_miniplayer_infobar\u003dtrue\u0026web_cinematic_watch_settings\u003dtrue\u0026web_client_version_override\u003d\u0026web_csi_action_sampling_enabled\u003dtrue\u0026web_csi_debug_sample_enabled\u003dtrue\u0026web_dedupe_ve_grafting\u003dtrue\u0026web_disable_channels_chapter_entrypoint\u003dtrue\u0026web_enable_ab_em_rsp\u003dtrue\u0026web_enable_ab_rsp_cl\u003dtrue\u0026web_enable_abd_ref\u003dtrue\u0026web_enable_course_icon_update\u003dtrue\u0026web_enable_error_204\u003dtrue\u0026web_enable_keyboard_shortcut_for_timely_actions\u003dtrue\u0026web_enable_sink_animated_actions\u003dtrue\u0026web_enable_sink_avatars_batch\u003dtrue\u0026web_enable_sink_checkbox_shape\u003dtrue\u0026web_enable_sink_dialog_header_view_model\u003dtrue\u0026web_enable_sink_list_view_models\u003dtrue\u0026web_enable_sink_lockups_batch\u003dtrue\u0026web_enable_sink_page_header_view_model\u003dtrue\u0026web_enable_sink_radio_shape\u003dtrue\u0026web_enable_sink_section_header\u003dtrue\u0026web_enable_sink_smartimations\u003dtrue\u0026web_enable_sink_video_summary_content_view_model\u003dtrue\u0026web_enable_sink_yt_description_preview_view_model\u003dtrue\u0026web_enable_sink_yt_page_header_renderer\u003dtrue\u0026web_enable_sink_yt_subscribe_button_view_model\u003dtrue\u0026web_enable_speedmaster\u003dtrue\u0026web_enable_timely_actions\u003dtrue\u0026web_fix_fine_scrubbing_false_play\u003dtrue\u0026web_foreground_heartbeat_interval_ms\u003d28000\u0026web_forward_command_on_pbj\u003dtrue\u0026web_fullscreen_shorts\u003dtrue\u0026web_gcf_hashes_innertube\u003dtrue\u0026web_gel_debounce_ms\u003d60000\u0026web_gel_timeout_cap\u003dtrue\u0026web_heat_map_v2\u003dtrue\u0026web_honor_cache_for_back\u003dtrue\u0026web_infocards_teaser_show_logging_fix\u003dtrue\u0026web_key_moments_markers\u003dtrue\u0026web_l3_storyboard\u003dtrue\u0026web_log_memory_total_kbytes\u003dtrue\u0026web_logging_max_batch\u003d150\u0026web_masthead_visited_channel_color_fix\u003dtrue\u0026web_max_tracing_events\u003d50\u0026web_modern_ads\u003dtrue\u0026web_modern_buttons\u003dtrue\u0026web_modern_buttons_bl_survey\u003dtrue\u0026web_new_autonav_countdown\u003dtrue\u0026web_one_platform_error_handling\u003dtrue\u0026web_op_signal_type_banlist\u003d[]\u0026web_playback_associated_log_ctt\u003dtrue\u0026web_playback_associated_ve\u003dtrue\u0026web_player_api_logging_fraction\u003d0.01\u0026web_player_audio_playback_from_audio_config\u003dtrue\u0026web_player_autonav_empty_suggestions_fix\u003dtrue\u0026web_player_autonav_next_button_renderer\u003dtrue\u0026web_player_autonav_toggle_always_listen\u003dtrue\u0026web_player_autonav_use_server_provided_state\u003dtrue\u0026web_player_disable_inline_scrubbing\u003dtrue\u0026web_player_enable_cultural_moment_overlay\u003dtrue\u0026web_player_enable_featured_product_banner_exclusives_on_desktop\u003dtrue\u0026web_player_enable_featured_product_banner_promotion_text_on_desktop\u003dtrue\u0026web_player_enable_overflow_button_in_banner_on_desktop\u003dtrue\u0026web_player_enable_premium_hbr_in_h5_api\u003dtrue\u0026web_player_enable_premium_hbr_playback_cap\u003dtrue\u0026web_player_innertube_playlist_update\u003dtrue\u0026web_player_ipp_canary_type_for_logging\u003d\u0026web_player_log_click_before_generating_ve_conversion_params\u003dtrue\u0026web_player_move_autonav_toggle\u003dtrue\u0026web_player_music_visualizer_treatment\u003dfake\u0026web_player_nitrate_promo_tooltip\u003dtrue\u0026web_player_offline_playlist_auto_refresh\u003dtrue\u0026web_player_pt_tt\u003dtrue\u0026web_player_rtr_ctrls\u003dtrue\u0026web_player_seek_chapters_by_shortcut\u003dtrue\u0026web_player_seek_overlay_additional_arrow_threshold\u003d200\u0026web_player_seek_overlay_duration_bump_scale\u003d0.95\u0026web_player_seek_overlay_linger_duration\u003d1000\u0026web_player_sentinel_is_uniplayer\u003dtrue\u0026web_player_shorts_audio_pivot_event_label\u003dtrue\u0026web_player_should_honor_include_asr_setting\u003dtrue\u0026web_player_show_music_in_this_video_graphic\u003dvideo_thumbnail\u0026web_player_small_hbp_settings_menu\u003dtrue\u0026web_player_spacebar_control_bugfix\u003dtrue\u0026web_player_split_event_bus\u003dtrue\u0026web_player_ss_dai_ad_fetching_timeout_ms\u003d15000\u0026web_player_ss_media_time_offset\u003dtrue\u0026web_player_transfer_timeout_threshold_ms\u003d10800000\u0026web_player_use_cinematic_label_2\u003dtrue\u0026web_player_use_heartbeat_poll_delay_ms\u003dtrue\u0026web_player_use_new_api_for_quality_pullback\u003dtrue\u0026web_player_ve_conversion_fixes_for_channel_info\u003dtrue\u0026web_prefetch_preload_video\u003dtrue\u0026web_remix_allow_up_to_3x_playback_rate\u003dtrue\u0026web_resizable_advertiser_banner_on_masthead_safari_fix\u003dtrue\u0026web_rounded_thumbnails\u003dtrue\u0026web_scheduler_auto_init\u003dtrue\u0026web_settings_menu_icons\u003dtrue\u0026web_settings_menu_surface_custom_playback\u003dtrue\u0026web_settings_use_input_slider\u003dtrue\u0026web_shorts_pivot_button_view_model_reactive\u003dtrue\u0026web_sleep_timer\u003dtrue\u0026web_speedmaster_spacebar_control\u003dtrue\u0026web_touch_feedback_changes\u003dtrue\u0026web_yt_config_context\u003dtrue\u0026webfe_disable_ab_em_plb\u003dtrue\u0026wil_icon_max_concurrent_fetches\u003d9999\u0026wil_icon_render_when_idle\u003dtrue\u0026wiz_diff_overwritable\u003dtrue\u0026wiz_memoize_stamper_items\u003dtrue\u0026wiz_use_generic_logging_infra\u003dtrue\u0026woffle_clean_up_after_entity_migration\u003dtrue\u0026woffle_enable_download_status\u003dtrue\u0026woffle_playlist_optimization\u003dtrue\u0026woffle_used_state_report\u003dtrue\u0026write_reload_player_response_token_to_ustreamer_config\u003dtrue\u0026write_reload_player_response_token_to_ustreamer_config_for_vod\u003dtrue\u0026ytidb_clear_embedded_player\u003dtrue\u0026ytidb_fetch_datasync_ids_for_data_cleanup\u003dtrue\u0026ytidb_remake_db_retries\u003d1\u0026ytidb_reopen_db_retries\u003d0\u0026ytidb_transaction_ended_event_rate_limit\u003d0.02","cspNonce":"mWvYBNEsFc3nGnwaOCaRHg","canaryState":"none","enableCsiLogging":true,"csiPageType":"watch","datasyncId":"V23cf27ca||","allowWoffleManagement":true,"cinematicSettingsAvailable":true,"canaryStage":""},"WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_CHANNEL_TRAILER":{"rootElementId":"c4-player","jsUrl":"/s/player/fc2a56a5/player_ias.vflset/en_US/base.js","cssUrl":"/s/player/fc2a56a5/www-player.css","contextId":"WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_CHANNEL_TRAILER","eventLabel":"profilepage","contentRegion":"DE","hl":"en_US","hostLanguage":"en","playerStyle":"desktop-polymer","innertubeApiKey":"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8","innertubeApiVersion":"v1","innertubeContextClientVersion":"2.20250610.00.00","device":{"brand":"robot","model":"bot or crawler","platform":"DESKTOP","interfaceName":"WEB","interfaceVersion":"2.20250610.00.00"},"serializedExperimentIds":"23986034,24004644,24439361,24566687,24699899,39325854,51010235,51063643,51072748,51091058,51095478,51098299,51204329,51222973,51237842,51285052,51300176,51300241,51313767,51314158,51318838,51340662,51342504,51349914,51353393,51354083,51355912,51366423,51389629,51397332,51404808,51404810,51409334,51425033,51430311,51432529,51439763,51439874,51444283,51447754,51456629,51459424,51462020,51462839,51463930,51467676,51470301,51471144,51471920,51472817,51475593,51476898,51477232,51478931,51479906,51481241,51481410,51481591,51481788,51483631,51484222,51486018,51488801,51489568,51490331,51490995,51492252,51492929,51493010,51494560,51494655,51496969,51497258,51498459,51500336,51505436,51508739,51509681,51509830,51509857,51510888,51511440,51513519,51516610","serializedExperimentFlags":"H5_async_logging_delay_ms\u003d30000.0\u0026H5_enable_full_pacf_logging\u003dtrue\u0026H5_use_async_logging\u003dtrue\u0026a11y_h5_associate_survey_question\u003dtrue\u0026ab_det_apb_b\u003dtrue\u0026ab_det_apm\u003dtrue\u0026ab_det_el_h\u003dtrue\u0026ab_det_em_inj\u003dtrue\u0026ab_l_sig_st\u003dtrue\u0026ab_l_sig_st_e\u003dtrue\u0026ab_sa_ef\u003dtrue\u0026action_companion_center_align_description\u003dtrue\u0026ad_pod_disable_companion_persist_ads_quality\u003dtrue\u0026align_three_dot_menu_with_title_description\u003dtrue\u0026allow_drm_override\u003dtrue\u0026allow_live_autoplay\u003dtrue\u0026allow_poltergust_autoplay\u003dtrue\u0026allow_proxima_live_latency\u003dtrue\u0026allow_skip_networkless\u003dtrue\u0026allow_vp9_1080p_mq_enc\u003dtrue\u0026always_cache_redirect_endpoint\u003dtrue\u0026assign_drm_family_by_format\u003dtrue\u0026att_web_record_metrics\u003dtrue\u0026attmusi\u003dtrue\u0026autoplay_time\u003d8000\u0026autoplay_time_for_fullscreen\u003d3000\u0026autoplay_time_for_music_content\u003d3000\u0026bg_st_hr\u003dtrue\u0026bg_vm_reinit_threshold\u003d7200000\u0026blocked_packages_for_sps\u003d[]\u0026botguard_async_snapshot_timeout_ms\u003d3000\u0026captions_url_add_ei\u003dtrue\u0026check_navigator_accuracy_timeout_ms\u003d0\u0026clean_player_style_fix_on_web\u003dtrue\u0026clean_up_manual_attribution_header\u003dtrue\u0026clear_user_partitioned_ls\u003dtrue\u0026client_respect_autoplay_switch_button_renderer\u003dtrue\u0026cobalt_h5vcc_h_t_t_p3\u003d0\u0026cobalt_h5vcc_media_dot_async_release_media_codec_bridge\u003d0\u0026cobalt_h5vcc_media_dot_audio_write_duration_local\u003d0\u0026cobalt_h5vcc_media_dot_player_configuration_dot_decode_to_texture_preferred\u003d0\u0026cobalt_h5vcc_media_dot_set_async_release_media_codec_bridge_timeout_seconds\u003d-1\u0026cobalt_h5vcc_media_element_dot_enable_using_media_source_attachment_methods\u003d0\u0026cobalt_h5vcc_media_element_dot_enable_using_media_source_buffered_range\u003d0\u0026cobalt_h5vcc_q_u_i_c\u003d0\u0026cobalt_h5vcc_set_prefer_minimal_post_processing\u003d0\u0026cobalt_h5vcc_string_q_u_i_c_connection_options\u003d\u0026compress_gel\u003dtrue\u0026compression_disable_point\u003d10\u0026cow_optimize_idom_compat\u003dtrue\u0026csi_config_handling_infra\u003dtrue\u0026csi_on_gel\u003dtrue\u0026custom_active_view_tos_timeout_ms\u003d3600000\u0026dash_manifest_version\u003d5\u0026debug_bandaid_hostname\u003d\u0026debug_sherlog_username\u003d\u0026delhi_modern_web_player_blending_mode\u003d\u0026deprecate_22\u003dtrue\u0026deprecate_csi_has_info\u003dtrue\u0026deprecate_delay_ping\u003dtrue\u0026deprecate_pair_servlet_enabled\u003dtrue\u0026desktop_sparkles_light_cta_button\u003dtrue\u0026disable_ad_duration_remaining_for_instream_video_ads\u003dtrue\u0026disable_ad_preview_for_instream_ads\u003dtrue\u0026disable_cached_masthead_data\u003dtrue\u0026disable_channel_id_check_for_suspended_channels\u003dtrue\u0026disable_child_node_auto_formatted_strings\u003dtrue\u0026disable_enf_isd\u003dtrue\u0026disable_features_for_supex\u003dtrue\u0026disable_legacy_desktop_remote_queue\u003dtrue\u0026disable_log_to_visitor_layer\u003dtrue\u0026disable_mdx_connection_in_mdx_module_for_music_web\u003dtrue\u0026disable_pacf_logging_for_memory_limited_tv\u003dtrue\u0026disable_reel_item_watch_format_filtering\u003dtrue\u0026disable_rounding_ad_notify\u003dtrue\u0026disable_ssdai_on_errors\u003dtrue\u0026disable_threegpp_progressive_formats\u003dtrue\u0026edge_encryption_fill_primary_key_version\u003dtrue\u0026embeds_disable_play_button_ve_focus_check\u003dtrue\u0026embeds_enable_autoplay_and_visibility_signals\u003dtrue\u0026embeds_enable_embedder_id_db_for_pfedu\u003dtrue\u0026embeds_enable_embedder_identity\u003dtrue\u0026embeds_enable_emc3ds_muted_autoplay\u003dtrue\u0026embeds_enable_emc3ds_playlist_buttons\u003dtrue\u0026embeds_enable_move_set_center_crop_to_public\u003dtrue\u0026embeds_enable_muted_autoplay_shorts_endscreen_fix\u003dtrue\u0026embeds_enable_pfp_always_unbranded
gitextract_aofas8tg/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── pyproject.toml
└── youtube_transcript_api/
├── __init__.py
├── __main__.py
├── _api.py
├── _cli.py
├── _errors.py
├── _settings.py
├── _transcripts.py
├── formatters.py
├── proxies.py
├── py.typed
└── test/
├── __init__.py
├── assets/
│ ├── __init__.py
│ ├── transcript.xml.static
│ ├── youtube.html.static
│ ├── youtube.innertube.json.static
│ ├── youtube_age_restricted.innertube.json.static
│ ├── youtube_altered_user_agent.innertube.json.static
│ ├── youtube_consent_page.html.static
│ ├── youtube_consent_page_invalid.html.static
│ ├── youtube_po_token_required.innertube.json.static
│ ├── youtube_request_blocked.innertube.json.static
│ ├── youtube_too_many_requests.html.static
│ ├── youtube_transcripts_disabled.innertube.json.static
│ ├── youtube_transcripts_disabled2.innertube.json.static
│ ├── youtube_unplayable.innertube.json.static
│ ├── youtube_video_unavailable.innertube.json.static
│ └── youtube_ww1_nl_en.innertube.json.static
├── test_api.py
├── test_cli.py
├── test_formatters.py
└── test_proxies.py
SYMBOL INDEX (226 symbols across 11 files)
FILE: youtube_transcript_api/__main__.py
function main (line 8) | def main():
FILE: youtube_transcript_api/_api.py
class YouTubeTranscriptApi (line 12) | class YouTubeTranscriptApi:
method __init__ (line 13) | def __init__(
method fetch (line 51) | def fetch(
method list (line 76) | def list(
FILE: youtube_transcript_api/_cli.py
class YouTubeTranscriptCli (line 11) | class YouTubeTranscriptCli:
method __init__ (line 12) | def __init__(self, args: List[str]):
method run (line 15) | def run(self) -> str:
method _fetch_transcript (line 74) | def _fetch_transcript(
method _get_version (line 95) | def _get_version(self):
method _parse_args (line 101) | def _parse_args(self):
method _sanitize_video_ids (line 201) | def _sanitize_video_ids(self, args):
FILE: youtube_transcript_api/_errors.py
class YouTubeTranscriptApiException (line 10) | class YouTubeTranscriptApiException(Exception):
class CookieError (line 14) | class CookieError(YouTubeTranscriptApiException):
class CookiePathInvalid (line 18) | class CookiePathInvalid(CookieError):
method __init__ (line 19) | def __init__(
class CookieInvalid (line 25) | class CookieInvalid(CookieError):
method __init__ (line 26) | def __init__(
class CouldNotRetrieveTranscript (line 34) | class CouldNotRetrieveTranscript(YouTubeTranscriptApiException):
method __init__ (line 51) | def __init__(self, video_id: str):
method _build_error_message (line 55) | def _build_error_message(self) -> str:
method cause (line 69) | def cause(self) -> str:
method __str__ (line 72) | def __str__(self) -> str:
class YouTubeDataUnparsable (line 76) | class YouTubeDataUnparsable(CouldNotRetrieveTranscript):
class YouTubeRequestFailed (line 83) | class YouTubeRequestFailed(CouldNotRetrieveTranscript):
method __init__ (line 86) | def __init__(self, video_id: str, http_error: HTTPError):
method cause (line 91) | def cause(self) -> str:
class VideoUnplayable (line 97) | class VideoUnplayable(CouldNotRetrieveTranscript):
method __init__ (line 101) | def __init__(self, video_id: str, reason: Optional[str], sub_reasons: ...
method cause (line 107) | def cause(self):
class VideoUnavailable (line 119) | class VideoUnavailable(CouldNotRetrieveTranscript):
class InvalidVideoId (line 123) | class InvalidVideoId(CouldNotRetrieveTranscript):
class RequestBlocked (line 131) | class RequestBlocked(CouldNotRetrieveTranscript):
method __init__ (line 182) | def __init__(self, video_id: str):
method with_proxy_config (line 186) | def with_proxy_config(
method cause (line 193) | def cause(self) -> str:
class IpBlocked (line 201) | class IpBlocked(RequestBlocked):
class TranscriptsDisabled (line 211) | class TranscriptsDisabled(CouldNotRetrieveTranscript):
class AgeRestricted (line 215) | class AgeRestricted(CouldNotRetrieveTranscript):
class NotTranslatable (line 232) | class NotTranslatable(CouldNotRetrieveTranscript):
class TranslationLanguageNotAvailable (line 236) | class TranslationLanguageNotAvailable(CouldNotRetrieveTranscript):
class FailedToCreateConsentCookie (line 240) | class FailedToCreateConsentCookie(CouldNotRetrieveTranscript):
class NoTranscriptFound (line 244) | class NoTranscriptFound(CouldNotRetrieveTranscript):
method __init__ (line 250) | def __init__(
method cause (line 261) | def cause(self) -> str:
class PoTokenRequired (line 268) | class PoTokenRequired(CouldNotRetrieveTranscript):
FILE: youtube_transcript_api/_transcripts.py
class FetchedTranscriptSnippet (line 35) | class FetchedTranscriptSnippet:
class FetchedTranscript (line 50) | class FetchedTranscript:
method __iter__ (line 62) | def __iter__(self) -> Iterator[FetchedTranscriptSnippet]:
method __getitem__ (line 65) | def __getitem__(self, index) -> FetchedTranscriptSnippet:
method __len__ (line 68) | def __len__(self) -> int:
method to_raw_data (line 71) | def to_raw_data(self) -> List[Dict]:
class _TranslationLanguage (line 76) | class _TranslationLanguage:
class _PlayabilityStatus (line 81) | class _PlayabilityStatus(str, Enum):
class _PlayabilityFailedReason (line 87) | class _PlayabilityFailedReason(str, Enum):
function _raise_http_errors (line 93) | def _raise_http_errors(response: Response, video_id: str) -> Response:
class Transcript (line 103) | class Transcript:
method __init__ (line 104) | def __init__(
method fetch (line 130) | def fetch(self, preserve_formatting: bool = False) -> FetchedTranscript:
method __str__ (line 149) | def __str__(self) -> str:
method is_translatable (line 157) | def is_translatable(self) -> bool:
method translate (line 160) | def translate(self, language_code: str) -> "Transcript":
class TranscriptList (line 180) | class TranscriptList:
method __init__ (line 186) | def __init__(
method build (line 207) | def build(
method __iter__ (line 252) | def __iter__(self) -> Iterator[Transcript]:
method find_transcript (line 258) | def find_transcript(self, language_codes: Iterable[str]) -> Transcript:
method find_generated_transcript (line 274) | def find_generated_transcript(self, language_codes: Iterable[str]) -> ...
method find_manually_created_transcript (line 285) | def find_manually_created_transcript(
method _find_transcript (line 300) | def _find_transcript(
method __str__ (line 312) | def __str__(self) -> str:
method _get_language_description (line 339) | def _get_language_description(self, transcript_strings: Iterable[str])...
class TranscriptListFetcher (line 347) | class TranscriptListFetcher:
method __init__ (line 348) | def __init__(self, http_client: Session, proxy_config: Optional[ProxyC...
method fetch (line 352) | def fetch(self, video_id: str) -> TranscriptList:
method _fetch_captions_json (line 359) | def _fetch_captions_json(self, video_id: str, try_number: int = 0) -> ...
method _extract_innertube_api_key (line 375) | def _extract_innertube_api_key(self, html: str, video_id: str) -> str:
method _extract_captions_json (line 384) | def _extract_captions_json(self, innertube_data: Dict, video_id: str) ...
method _assert_playability (line 395) | def _assert_playability(self, playability_status_data: Dict, video_id:...
method _create_consent_cookie (line 424) | def _create_consent_cookie(self, html: str, video_id: str) -> None:
method _fetch_video_html (line 432) | def _fetch_video_html(self, video_id: str) -> str:
method _fetch_html (line 441) | def _fetch_html(self, video_id: str) -> str:
method _fetch_innertube_data (line 445) | def _fetch_innertube_data(self, video_id: str, api_key: str) -> Dict:
class _TranscriptParser (line 457) | class _TranscriptParser:
method __init__ (line 471) | def __init__(self, preserve_formatting: bool = False):
method _get_html_regex (line 474) | def _get_html_regex(self, preserve_formatting: bool) -> Pattern[str]:
method parse (line 483) | def parse(self, raw_data: str) -> List[FetchedTranscriptSnippet]:
FILE: youtube_transcript_api/formatters.py
class Formatter (line 9) | class Formatter:
method format_transcript (line 17) | def format_transcript(self, transcript: FetchedTranscript, **kwargs) -...
method format_transcripts (line 23) | def format_transcripts(self, transcripts: List[FetchedTranscript], **k...
class PrettyPrintFormatter (line 30) | class PrettyPrintFormatter(Formatter):
method format_transcript (line 31) | def format_transcript(self, transcript: FetchedTranscript, **kwargs) -...
method format_transcripts (line 39) | def format_transcripts(self, transcripts: List[FetchedTranscript], **k...
class JSONFormatter (line 50) | class JSONFormatter(Formatter):
method format_transcript (line 51) | def format_transcript(self, transcript: FetchedTranscript, **kwargs) -...
method format_transcripts (line 59) | def format_transcripts(self, transcripts: List[FetchedTranscript], **k...
class TextFormatter (line 70) | class TextFormatter(Formatter):
method format_transcript (line 71) | def format_transcript(self, transcript: FetchedTranscript, **kwargs) -...
method format_transcripts (line 79) | def format_transcripts(self, transcripts: List[FetchedTranscript], **k...
class _TextBasedFormatter (line 90) | class _TextBasedFormatter(TextFormatter):
method _format_timestamp (line 91) | def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int)...
method _format_transcript_header (line 97) | def _format_transcript_header(self, lines: Iterable[str]) -> str:
method _format_transcript_helper (line 103) | def _format_transcript_helper(
method _seconds_to_timestamp (line 111) | def _seconds_to_timestamp(self, time: float) -> str:
method format_transcript (line 130) | def format_transcript(self, transcript: FetchedTranscript, **kwargs) -...
class SRTFormatter (line 154) | class SRTFormatter(_TextBasedFormatter):
method _format_timestamp (line 155) | def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int)...
method _format_transcript_header (line 158) | def _format_transcript_header(self, lines: Iterable[str]) -> str:
method _format_transcript_helper (line 161) | def _format_transcript_helper(
class WebVTTFormatter (line 167) | class WebVTTFormatter(_TextBasedFormatter):
method _format_timestamp (line 168) | def _format_timestamp(self, hours: int, mins: int, secs: int, ms: int)...
method _format_transcript_header (line 171) | def _format_transcript_header(self, lines: Iterable[str]) -> str:
method _format_transcript_helper (line 174) | def _format_transcript_helper(
class FormatterLoader (line 180) | class FormatterLoader:
class UnknownFormatterType (line 189) | class UnknownFormatterType(Exception):
method __init__ (line 190) | def __init__(self, formatter_type: str):
method load (line 199) | def load(self, formatter_type: str = "pretty") -> Formatter:
FILE: youtube_transcript_api/proxies.py
class InvalidProxyConfig (line 5) | class InvalidProxyConfig(Exception):
class RequestsProxyConfigDict (line 9) | class RequestsProxyConfigDict(TypedDict):
class ProxyConfig (line 20) | class ProxyConfig(ABC):
method to_requests_dict (line 27) | def to_requests_dict(self) -> RequestsProxyConfigDict:
method prevent_keeping_connections_alive (line 36) | def prevent_keeping_connections_alive(self) -> bool:
method retries_when_blocked (line 45) | def retries_when_blocked(self) -> int:
class GenericProxyConfig (line 55) | class GenericProxyConfig(ProxyConfig):
method __init__ (line 66) | def __init__(self, http_url: Optional[str] = None, https_url: Optional...
method to_requests_dict (line 84) | def to_requests_dict(self) -> RequestsProxyConfigDict:
class WebshareProxyConfig (line 91) | class WebshareProxyConfig(GenericProxyConfig):
method __init__ (line 116) | def __init__(
method url (line 159) | def url(self) -> str:
method http_url (line 173) | def http_url(self) -> str:
method https_url (line 177) | def https_url(self) -> str:
method prevent_keeping_connections_alive (line 181) | def prevent_keeping_connections_alive(self) -> bool:
method retries_when_blocked (line 185) | def retries_when_blocked(self) -> int:
FILE: youtube_transcript_api/test/test_api.py
function get_asset_path (line 34) | def get_asset_path(filename: str) -> Path:
function load_asset (line 42) | def load_asset(filename: str):
class TestYouTubeTranscriptApi (line 47) | class TestYouTubeTranscriptApi(TestCase):
method setUp (line 48) | def setUp(self):
method test_fetch (line 92) | def test_fetch(self):
method test_fetch_formatted (line 100) | def test_fetch_formatted(self):
method test_fetch__with_altered_user_agent (line 112) | def test_fetch__with_altered_user_agent(self):
method test_list (line 126) | def test_list(self):
method test_list__find_manually_created (line 135) | def test_list__find_manually_created(self):
method test_list__find_generated (line 141) | def test_list__find_generated(self):
method test_list__url_as_video_id (line 151) | def test_list__url_as_video_id(self):
method test_translate_transcript (line 163) | def test_translate_transcript(self):
method test_translate_transcript__translation_language_not_available (line 171) | def test_translate_transcript__translation_language_not_available(self):
method test_translate_transcript__not_translatable (line 177) | def test_translate_transcript__not_translatable(self):
method test_fetch__correct_language_is_used (line 184) | def test_fetch__correct_language_is_used(self):
method test_fetch__fallback_language_is_used (line 193) | def test_fetch__fallback_language_is_used(self):
method test_fetch__create_consent_cookie_if_needed (line 208) | def test_fetch__create_consent_cookie_if_needed(self):
method test_fetch__exception_if_create_consent_cookie_failed (line 229) | def test_fetch__exception_if_create_consent_cookie_failed(self):
method test_fetch__exception_if_consent_cookie_age_invalid (line 244) | def test_fetch__exception_if_consent_cookie_age_invalid(self):
method test_fetch__exception_if_video_unavailable (line 254) | def test_fetch__exception_if_video_unavailable(self):
method test_fetch__exception_if_youtube_request_fails (line 264) | def test_fetch__exception_if_youtube_request_fails(self):
method test_fetch__exception_if_youtube_request_limit_reached (line 274) | def test_fetch__exception_if_youtube_request_limit_reached(
method test_fetch__exception_if_timedtext_request_limit_reached (line 286) | def test_fetch__exception_if_timedtext_request_limit_reached(
method test_fetch__exception_if_age_restricted (line 298) | def test_fetch__exception_if_age_restricted(self):
method test_fetch__exception_if_ip_blocked (line 308) | def test_fetch__exception_if_ip_blocked(self):
method test_fetch__exception_if_po_token_required (line 318) | def test_fetch__exception_if_po_token_required(self):
method test_fetch__exception_request_blocked (line 328) | def test_fetch__exception_request_blocked(self):
method test_fetch__exception_unplayable (line 341) | def test_fetch__exception_unplayable(self):
method test_fetch__exception_if_transcripts_disabled (line 355) | def test_fetch__exception_if_transcripts_disabled(self):
method test_fetch__exception_if_language_unavailable (line 373) | def test_fetch__exception_if_language_unavailable(self):
method test_fetch__with_proxy (line 380) | def test_fetch__with_proxy(self, to_requests_dict):
method test_fetch__with_proxy_prevent_alive_connections (line 395) | def test_fetch__with_proxy_prevent_alive_connections(self, to_requests...
method test_fetch__with_proxy_retry_when_blocked (line 406) | def test_fetch__with_proxy_retry_when_blocked(self, to_requests_dict):
method test_fetch__with_webshare_proxy_reraise_when_blocked (line 437) | def test_fetch__with_webshare_proxy_reraise_when_blocked(self, to_requ...
method test_fetch__with_generic_proxy_reraise_when_blocked (line 467) | def test_fetch__with_generic_proxy_reraise_when_blocked(self, to_reque...
method test_fetch__with_cookies (line 491) | def test_fetch__with_cookies(self):
method test_load_cookies (line 504) | def test_load_cookies(self):
method test_load_cookies__bad_file_path (line 519) | def test_load_cookies__bad_file_path(self):
method test_load_cookies__no_valid_cookies (line 528) | def test_load_cookies__no_valid_cookies(self):
FILE: youtube_transcript_api/test/test_cli.py
class TestYouTubeTranscriptCli (line 18) | class TestYouTubeTranscriptCli(TestCase):
method setUp (line 19) | def setUp(self):
method test_argument_parsing (line 62) | def test_argument_parsing(self):
method test_argument_parsing__only_video_ids (line 130) | def test_argument_parsing__only_video_ids(self):
method test_argument_parsing__video_ids_starting_with_dash (line 136) | def test_argument_parsing__video_ids_starting_with_dash(self):
method test_argument_parsing__fail_without_video_ids (line 142) | def test_argument_parsing__fail_without_video_ids(self):
method test_argument_parsing__json (line 146) | def test_argument_parsing__json(self):
method test_argument_parsing__languages (line 157) | def test_argument_parsing__languages(self):
method test_argument_parsing__proxies (line 165) | def test_argument_parsing__proxies(self):
method test_argument_parsing__list_transcripts (line 186) | def test_argument_parsing__list_transcripts(self):
method test_argument_parsing__translate (line 199) | def test_argument_parsing__translate(self):
method test_argument_parsing__manually_or_generated (line 216) | def test_argument_parsing__manually_or_generated(self):
method test_run (line 238) | def test_run(self):
method test_run__failing_transcripts (line 246) | def test_run__failing_transcripts(self):
method test_run__exclude_generated (line 253) | def test_run__exclude_generated(self):
method test_run__exclude_manually_created (line 262) | def test_run__exclude_manually_created(self):
method test_run__exclude_manually_created_and_generated (line 271) | def test_run__exclude_manually_created_and_generated(self):
method test_run__translate (line 279) | def test_run__translate(self):
method test_run__list_transcripts (line 284) | def test_run__list_transcripts(self):
method test_run__json_output (line 290) | def test_run__json_output(self):
method test_run__webshare_proxy_config (line 298) | def test_run__webshare_proxy_config(self):
method test_run__generic_proxy_config (line 315) | def test_run__generic_proxy_config(self):
method test_run__cookies (line 336) | def test_run__cookies(self):
method test_version_matches_metadata (line 346) | def test_version_matches_metadata(self):
method test_get_version_package_not_found (line 365) | def test_get_version_package_not_found(self):
FILE: youtube_transcript_api/test/test_formatters.py
class TestFormatters (line 20) | class TestFormatters(TestCase):
method setUp (line 21) | def setUp(self):
method test_base_formatter_format_call (line 41) | def test_base_formatter_format_call(self):
method test_srt_formatter_starting (line 47) | def test_srt_formatter_starting(self):
method test_srt_formatter_middle (line 55) | def test_srt_formatter_middle(self):
method test_srt_formatter_ending (line 64) | def test_srt_formatter_ending(self):
method test_srt_formatter_many (line 72) | def test_srt_formatter_many(self):
method test_webvtt_formatter_starting (line 82) | def test_webvtt_formatter_starting(self):
method test_webvtt_formatter_ending (line 90) | def test_webvtt_formatter_ending(self):
method test_webvtt_formatter_many (line 98) | def test_webvtt_formatter_many(self):
method test_pretty_print_formatter (line 108) | def test_pretty_print_formatter(self):
method test_pretty_print_formatter_many (line 113) | def test_pretty_print_formatter_many(self):
method test_json_formatter (line 118) | def test_json_formatter(self):
method test_json_formatter_many (line 123) | def test_json_formatter_many(self):
method test_text_formatter (line 128) | def test_text_formatter(self):
method test_text_formatter_many (line 135) | def test_text_formatter_many(self):
method test_formatter_loader (line 145) | def test_formatter_loader(self):
method test_formatter_loader__default_formatter (line 151) | def test_formatter_loader__default_formatter(self):
method test_formatter_loader__unknown_format (line 157) | def test_formatter_loader__unknown_format(self):
FILE: youtube_transcript_api/test/test_proxies.py
class TestGenericProxyConfig (line 10) | class TestGenericProxyConfig:
method test_to_requests_dict (line 11) | def test_to_requests_dict(self):
method test_to_requests_dict__only_http (line 24) | def test_to_requests_dict__only_http(self):
method test_to_requests_dict__only_https (line 36) | def test_to_requests_dict__only_https(self):
method test__invalid_config (line 48) | def test__invalid_config(self):
class TestWebshareProxyConfig (line 53) | class TestWebshareProxyConfig:
method test_to_requests_dict (line 54) | def test_to_requests_dict(self):
method test_to_requests_dict__with_location_filter (line 67) | def test_to_requests_dict__with_location_filter(self):
method test_to_requests_dict__with_multiple_location_filters (line 81) | def test_to_requests_dict__with_multiple_location_filters(self):
method test_to_requests_dict__with_rotate_suffix_in_username (line 95) | def test_to_requests_dict__with_rotate_suffix_in_username(self):
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,318K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 118,
"preview": "github: jdepoix\ncustom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BAENLEW8VUJ6G&source=url\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1015,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nDO NOT DELETE TH"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 741,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/workflows/ci.yml",
"chars": 2546,
"preview": "name: CI\n\non:\n push:\n branches: [ \"master\" ]\n tags:\n - '**'\n pull_request:\n\njobs:\n static-checks:\n runs"
},
{
"path": ".gitignore",
"chars": 105,
"preview": ".idea\n.venv\nvirtualenv\n*.pyc\ndist\nbuild\n*.egg-info\nupload_new_version.sh\n.coverage\ncoverage.xml\n.DS_STORE"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2018 Jonas Depoix\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 23510,
"preview": "<h1 align=\"center\">\r\n ✨ YouTube Transcript API ✨\r\n</h1>\r\n\r\n<p align=\"center\">\r\n <a href=\"https://github.com/sponsors/j"
},
{
"path": "pyproject.toml",
"chars": 2806,
"preview": "[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.poetry]\nname = \"youtube-trans"
},
{
"path": "youtube_transcript_api/__init__.py",
"chars": 1292,
"preview": "# ruff: noqa: F401\nfrom ._api import YouTubeTranscriptApi\nfrom ._transcripts import (\n TranscriptList,\n Transcript"
},
{
"path": "youtube_transcript_api/__main__.py",
"chars": 200,
"preview": "import sys\n\nimport logging\n\nfrom ._cli import YouTubeTranscriptCli\n\n\ndef main():\n logging.basicConfig()\n\n print(Yo"
},
{
"path": "youtube_transcript_api/_api.py",
"chars": 5756,
"preview": "from typing import Optional, Iterable\n\nfrom requests import Session\nfrom requests.adapters import HTTPAdapter\nfrom urlli"
},
{
"path": "youtube_transcript_api/_cli.py",
"chars": 7325,
"preview": "import argparse\nfrom importlib.metadata import PackageNotFoundError, version\nfrom typing import List\n\nfrom .proxies impo"
},
{
"path": "youtube_transcript_api/_errors.py",
"chars": 10620,
"preview": "from pathlib import Path\nfrom typing import Iterable, Optional, List\n\nfrom requests import HTTPError\n\nfrom ._settings im"
},
{
"path": "youtube_transcript_api/_settings.py",
"chars": 223,
"preview": "WATCH_URL = \"https://www.youtube.com/watch?v={video_id}\"\nINNERTUBE_API_URL = \"https://www.youtube.com/youtubei/v1/player"
},
{
"path": "youtube_transcript_api/_transcripts.py",
"chars": 18420,
"preview": "from dataclasses import dataclass, asdict\nfrom enum import Enum\nfrom itertools import chain\n\nfrom html import unescape\nf"
},
{
"path": "youtube_transcript_api/formatters.py",
"chars": 7637,
"preview": "import json\n\nimport pprint\nfrom typing import List, Iterable\n\nfrom ._transcripts import FetchedTranscript, FetchedTransc"
},
{
"path": "youtube_transcript_api/proxies.py",
"chars": 7543,
"preview": "from abc import ABC, abstractmethod\nfrom typing import TypedDict, Optional, List\n\n\nclass InvalidProxyConfig(Exception):\n"
},
{
"path": "youtube_transcript_api/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "youtube_transcript_api/test/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": "youtube_transcript_api/test/assets/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": "youtube_transcript_api/test/assets/transcript.xml.static",
"chars": 344,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<transcript>\n <text start=\"0\" dur=\"1.54\">Hey, this is just a test</text>\n "
},
{
"path": "youtube_transcript_api/test/assets/youtube.html.static",
"chars": 1023580,
"preview": "<!DOCTYPE html><html style=\"font-size: 10px;font-family: Roboto, Arial, sans-serif;\" lang=\"en\" darker-dark-theme darker-"
},
{
"path": "youtube_transcript_api/test/assets/youtube.innertube.json.static",
"chars": 197583,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtmS09UUW54WUQ5VSjGu6bCBjIKCgJERRIEEgAgRDoMCAEg78fRoOW456RoWMrtgeuL0_SSmQE"
},
{
"path": "youtube_transcript_api/test/assets/youtube_age_restricted.innertube.json.static",
"chars": 11750,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtsM0ROUWd2dG5HayjHu6bCBjIKCgJERRIEEgAgYjoMCAEg1-KZt_C456RoWPrW4uqYgNyukgE"
},
{
"path": "youtube_transcript_api/test/assets/youtube_altered_user_agent.innertube.json.static",
"chars": 198357,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtaRDNwc2JaVUR6byjGu6bCBjIKCgJERRIEEgAgDDoMCAEgkbGFj-G456RoWLrMtY6smMCJlgE"
},
{
"path": "youtube_transcript_api/test/assets/youtube_consent_page.html.static",
"chars": 9214,
"preview": "<!DOCTYPE html><html lang=\"de\" dir=\"ltr\"><head><style nonce=\"8VuN4FiQoUKCWSNl9VnOhg\">\na, a:link, a:visited, a:active, a:"
},
{
"path": "youtube_transcript_api/test/assets/youtube_consent_page_invalid.html.static",
"chars": 9148,
"preview": "<!DOCTYPE html><html lang=\"de\" dir=\"ltr\"><head><style nonce=\"8VuN4FiQoUKCWSNl9VnOhg\">\na, a:link, a:visited, a:active, a:"
},
{
"path": "youtube_transcript_api/test/assets/youtube_po_token_required.innertube.json.static",
"chars": 197591,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtmS09UUW54WUQ5VSjGu6bCBjIKCgJERRIEEgAgRDoMCAEg78fRoOW456RoWMrtgeuL0_SSmQE"
},
{
"path": "youtube_transcript_api/test/assets/youtube_request_blocked.innertube.json.static",
"chars": 8152,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"Cgs3NmJkd2VWU1N2USj4uaLCBjIKCgJTQRIEGgAgJzoMCAEgkoPMhYCfp6RoWJfwuc_yqZ2rVQ%"
},
{
"path": "youtube_transcript_api/test/assets/youtube_too_many_requests.html.static",
"chars": 7067,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>YouTube</title>\n <script\n src=\"https://www.google.com/recaptcha/api.j"
},
{
"path": "youtube_transcript_api/test/assets/youtube_transcripts_disabled.innertube.json.static",
"chars": 160227,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtYbjcwUk43dy0tUSjGu6bCBjIKCgJERRIEEgAgJToMCAEgnvi2w-C456RoWKb_qomKiYjOfQ%"
},
{
"path": "youtube_transcript_api/test/assets/youtube_transcripts_disabled2.innertube.json.static",
"chars": 121782,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtBRTczM3F5enZYayjGu6bCBjIKCgJERRIEEgAgLjoMCAEggfjTkue456RoWM6XuL7v0sS8ygE"
},
{
"path": "youtube_transcript_api/test/assets/youtube_unplayable.innertube.json.static",
"chars": 8102,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"Cgs3NmJkd2VWU1N2USj4uaLCBjIKCgJTQRIEGgAgJzoMCAEgkoPMhYCfp6RoWJfwuc_yqZ2rVQ%"
},
{
"path": "youtube_transcript_api/test/assets/youtube_video_unavailable.innertube.json.static",
"chars": 7850,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtkVnYwR1MzN3pQTSjGu6bCBjIKCgJERRIEEgAgLjoMCAEgzKLJvOe456RoWJff7Nfpi977SA%"
},
{
"path": "youtube_transcript_api/test/assets/youtube_ww1_nl_en.innertube.json.static",
"chars": 109714,
"preview": "{\n \"responseContext\": {\n \"visitorData\": \"CgtseDdUcUhFTmhfTSjGu6bCBjIKCgJERRIEEgAgPToMCAEgloDE8eW456RoWK-nvta99dzy5wE"
},
{
"path": "youtube_transcript_api/test/test_api.py",
"chars": 18811,
"preview": "import pytest\nimport os\nfrom pathlib import Path\nfrom unittest import TestCase\nfrom unittest.mock import patch\nfrom urll"
},
{
"path": "youtube_transcript_api/test/test_cli.py",
"chars": 15106,
"preview": "import pytest\nfrom importlib.metadata import PackageNotFoundError, version\nfrom unittest import TestCase\nfrom unittest.m"
},
{
"path": "youtube_transcript_api/test/test_formatters.py",
"chars": 5536,
"preview": "from unittest import TestCase\n\nimport json\n\nimport pprint\n\nfrom youtube_transcript_api.formatters import (\n FetchedTr"
},
{
"path": "youtube_transcript_api/test/test_proxies.py",
"chars": 3150,
"preview": "import pytest\n\nfrom youtube_transcript_api.proxies import (\n GenericProxyConfig,\n InvalidProxyConfig,\n Webshare"
}
]
About this extraction
This page contains the full source code of the jdepoix/youtube-transcript-api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (2.1 MB), approximately 553.0k tokens, and a symbol index with 226 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.