Repository: yt-dlp/yt-dlp
Pull request #6301 (2023-02-20 01:28:56)

[youtube:comments] Detect comment section loop
Fixes https://github.com/yt-dlp/yt-dlp/issues/6290

Authored by: coletdjnz

**IMPORTANT**: PRs without the template will be CLOSED

### Description of your *pull request* and other information

<!--

Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible

-->

ADD DESCRIPTION HERE

Fixes https://github.com/yt-dlp/yt-dlp/issues/6290

The above case has the entire comment section looping. This stops the comment extraction if it detects that.

Note that this will not stop any other kind of looping e.g. a single page repeating.

<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--

# PLEASE FOLLOW THE GUIDE BELOW

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
- Use *Preview* tab to see how your *pull request* will actually look like

-->

### Before submitting a *pull request* make sure you have:
- [X] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [X] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [X] Checked the code with [flake8](https://pypi.python.org/pypi/flake8) and [ran relevant tests](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions)

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check all of the following options that apply:
- [X] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [X] Fix or improvement to an extractor (Make sure to add/update tests)
- [ ] New extractor ([Piracy websites will not be accepted](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy))
- [ ] Core bug fix/improvement
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))

</details>


pukkandan: Wouldn't it be more robust to store all detected comment ids? This can detect similar loops in reply threads, for example
pukkandan: If that's not something you want to do, you can merge this
coletdjnz: > Wouldn't it be more robust to store all detected comment ids in a set? This can detect similar loops in reply threads, for example

Yeah that's another thing I was considering, but it may have more of a memory impact.
pukkandan: Not really:
```py
In [18]: import string, random, sys

In [19]: x = { ''.join(random.choices(string.ascii_letters + string.digits, k=50)) for _ in range(1000000) }

In [20]: sys.getsizeof(x)/(2**20)
Out[20]: 32.000205993652344
```

JustAnotherArchivist: @pukkandan That's only the size of the set object itself and does not include the size of the strings within. Each of those is 75 bytes per my testing (which seems right; the empty string object is 49 byte, plus 26 bytes), so that adds another 75 MB per million comments. Might still be fine of course depending on who you ask.
ivan: I confirmed that this PR fixed the problem with https://www.youtube.com/watch?v=kOHXmlRaTqU

```
[youtube] Downloading comment API JSON page 7 (173/420)
WARNING: [youtube] Detected comment section loop from YouTube. Stopping comment extraction.
[youtube] Extracted 173 comments
```
pukkandan: > That's only the size of the set object itself and does not include the size of the strings within. Each of those is 75 bytes per my testing (which seems right; the empty string object is 49 byte, plus 26 bytes), so that adds another 75 MB per million comments. Might still be fine of course depending on who you ask.

The strings are already in memory inside the whole comment json. Afaik, Python doesn't copy them it

Patch:
diff --git a/.github/ISSUE_TEMPLATE/1_broken_site.yml b/.github/ISSUE_TEMPLATE/1_broken_site.yml
index e1103fb84..48e8890c5 100644
--- a/.github/ISSUE_TEMPLATE/1_broken_site.yml
+++ b/.github/ISSUE_TEMPLATE/1_broken_site.yml
@@ -50,6 +50,8 @@ body:
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
           required: true
   - type: textarea
diff --git a/.github/ISSUE_TEMPLATE/2_site_support_request.yml b/.github/ISSUE_TEMPLATE/2_site_support_request.yml
index 90d7294ac..d43d62f03 100644
--- a/.github/ISSUE_TEMPLATE/2_site_support_request.yml
+++ b/.github/ISSUE_TEMPLATE/2_site_support_request.yml
@@ -62,6 +62,8 @@ body:
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
           required: true
   - type: textarea
diff --git a/.github/ISSUE_TEMPLATE/3_site_feature_request.yml b/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
index 5b59852c7..352b47242 100644
--- a/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
@@ -58,6 +58,8 @@ body:
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
           required: true
   - type: textarea
diff --git a/.github/ISSUE_TEMPLATE/4_bug_report.yml b/.github/ISSUE_TEMPLATE/4_bug_report.yml
index bd4695f87..7588b8ed8 100644
--- a/.github/ISSUE_TEMPLATE/4_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/4_bug_report.yml
@@ -43,6 +43,8 @@ body:
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
           required: true
   - type: textarea
diff --git a/.github/ISSUE_TEMPLATE/5_feature_request.yml b/.github/ISSUE_TEMPLATE/5_feature_request.yml
index 8c7f315e9..fdda50b7b 100644
--- a/.github/ISSUE_TEMPLATE/5_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/5_feature_request.yml
@@ -40,6 +40,8 @@ body:
       label: Provide verbose output that clearly demonstrates the problem
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
   - type: textarea
     id: log
diff --git a/.github/ISSUE_TEMPLATE/6_question.yml b/.github/ISSUE_TEMPLATE/6_question.yml
index 4a1344628..56ce74654 100644
--- a/.github/ISSUE_TEMPLATE/6_question.yml
+++ b/.github/ISSUE_TEMPLATE/6_question.yml
@@ -46,6 +46,8 @@ body:
       label: Provide verbose output that clearly demonstrates the problem
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
   - type: textarea
     id: log
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 551db674e..ae2c45423 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -127,7 +127,7 @@ ### Are you willing to share account details if needed?
 
 ### Is the website primarily used for piracy?
 
-We follow [youtube-dl's policy](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) to not support services that is primarily used for infringing copyright. Additionally, it has been decided to not to support porn sites that specialize in deep fake. We also cannot support any service that serves only [DRM protected content](https://en.wikipedia.org/wiki/Digital_rights_management). 
+We follow [youtube-dl's policy](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) to not support services that is primarily used for infringing copyright. Additionally, it has been decided to not to support porn sites that specialize in fakes. We also cannot support any service that serves only [DRM protected content](https://en.wikipedia.org/wiki/Digital_rights_management). 
 
 
 
diff --git a/Changelog.md b/Changelog.md
index 8d3ac089c..24bc8a2e2 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -50,8 +50,8 @@ ### 2023.02.17
 * [extractor/txxx] Add extractors by [chio0hai](https://github.com/chio0hai)
 * [extractor/vocaroo] Add extractor by [SuperSonicHub1](https://github.com/SuperSonicHub1), [qbnu](https://github.com/qbnu)
 * [extractor/wrestleuniverse] Add extractors by [Grub4K](https://github.com/Grub4K), [bashonly](https://github.com/bashonly)
-* [extractor/yappy] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
-* **[extractor/youtube] Fix `uploader_id` extraction** by [bashonly](https://github.com/bashonly)
+* [extractor/yappy] Add extractor by [HobbyistDev](https://github.com/HobbyistDev), [dirkf](https://github.com/dirkf)
+* [extractor/youtube] **Fix `uploader_id` extraction** by [bashonly](https://github.com/bashonly)
 * [extractor/youtube] Add hyperpipe instances by [Generator](https://github.com/Generator)
 * [extractor/youtube] Handle `consent.youtube`
 * [extractor/youtube] Support `/live/` URL
diff --git a/README.md b/README.md
index 9b91775bc..3d3db933a 100644
--- a/README.md
+++ b/README.md
@@ -130,6 +130,7 @@ ### Differences in default behavior
 
 Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc:
 
+* yt-dlp supports only [Python 3.7+](## "Windows 7"), and *may* remove support for more versions as they [become EOL](https://devguide.python.org/versions/#python-release-cycle); while [youtube-dl still supports Python 2.6+ and 3.2+](https://github.com/ytdl-org/youtube-dl/issues/30568#issue-1118238743)
 * The options `--auto-number` (`-A`), `--title` (`-t`) and `--literal` (`-l`), no longer work. See [removed options](#Removed) for details
 * `avconv` is not supported as an alternative to `ffmpeg`
 * yt-dlp stores config files in slightly different locations to youtube-dl. See [CONFIGURATION](#configuration) for a list of correct locations
diff --git a/devscripts/make_issue_template.py b/devscripts/make_issue_template.py
index 1ee00f2b8..39b95c8da 100644
--- a/devscripts/make_issue_template.py
+++ b/devscripts/make_issue_template.py
@@ -24,6 +24,8 @@
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
+        - label: "If using API, add `'verbose': True` to `YoutubeDL` params instead"
+          required: false
         - label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
           required: true
   - type: textarea
diff --git a/supportedsites.md b/supportedsites.md
index b545ec540..d7ac6dce5 100644
--- a/supportedsites.md
+++ b/supportedsites.md
@@ -28,14 +28,14 @@ # Supported sites
  - **abcnews:video**
  - **abcotvs**: ABC Owned Television Stations
  - **abcotvs:clips**
- - **AbemaTV**: [<abbr title="netrc machine"><em>abematv</em></abbr>]
+ - **AbemaTV**: [*abematv*](## "netrc machine")
  - **AbemaTVTitle**
  - **AcademicEarth:Course**
  - **acast**
  - **acast:channel**
  - **AcFunBangumi**
  - **AcFunVideo**
- - **ADN**: [<abbr title="netrc machine"><em>animationdigitalnetwork</em></abbr>] Animation Digital Network
+ - **ADN**: [*animationdigitalnetwork*](## "netrc machine") Animation Digital Network
  - **AdobeConnect**
  - **adobetv**
  - **adobetv:channel**
@@ -47,8 +47,8 @@ # Supported sites
  - **aenetworks:collection**
  - **aenetworks:show**
  - **AeonCo**
- - **afreecatv**: [<abbr title="netrc machine"><em>afreecatv</em></abbr>] afreecatv.com
- - **afreecatv:live**: [<abbr title="netrc machine"><em>afreecatv</em></abbr>] afreecatv.com
+ - **afreecatv**: [*afreecatv*](## "netrc machine") afreecatv.com
+ - **afreecatv:live**: [*afreecatv*](## "netrc machine") afreecatv.com
  - **afreecatv:user**
  - **AirMozilla**
  - **AirTV**
@@ -59,8 +59,8 @@ # Supported sites
  - **AlphaPorno**
  - **Alsace20TV**
  - **Alsace20TVEmbed**
- - **Alura**: [<abbr title="netrc machine"><em>alura</em></abbr>]
- - **AluraCourse**: [<abbr title="netrc machine"><em>aluracourse</em></abbr>]
+ - **Alura**: [*alura*](## "netrc machine")
+ - **AluraCourse**: [*aluracourse*](## "netrc machine")
  - **Amara**
  - **AmazonMiniTV**
  - **amazonminitv:season**: Amazon MiniTV Season, "minitv:season:" prefix
@@ -100,7 +100,7 @@ # Supported sites
  - **ArteTVPlaylist**
  - **AsianCrush**
  - **AsianCrushPlaylist**
- - **AtresPlayer**: [<abbr title="netrc machine"><em>atresplayer</em></abbr>]
+ - **AtresPlayer**: [*atresplayer*](## "netrc machine")
  - **AtScaleConfEvent**
  - **ATTTechChannel**
  - **ATVAt**
@@ -128,15 +128,15 @@ # Supported sites
  - **Bandcamp:user**
  - **Bandcamp:weekly**
  - **BannedVideo**
- - **bbc**: [<abbr title="netrc machine"><em>bbc</em></abbr>] BBC
- - **bbc.co.uk**: [<abbr title="netrc machine"><em>bbc</em></abbr>] BBC iPlayer
+ - **bbc**: [*bbc*](## "netrc machine") BBC
+ - **bbc.co.uk**: [*bbc*](## "netrc machine") BBC iPlayer
  - **bbc.co.uk:article**: BBC articles
  - **bbc.co.uk:​iplayer:episodes**
  - **bbc.co.uk:​iplayer:group**
  - **bbc.co.uk:playlist**
- - **BBVTV**: [<abbr title="netrc machine"><em>bbvtv</em></abbr>]
- - **BBVTVLive**: [<abbr title="netrc machine"><em>bbvtv</em></abbr>]
- - **BBVTVRecordings**: [<abbr title="netrc machine"><em>bbvtv</em></abbr>]
+ - **BBVTV**: [*bbvtv*](## "netrc machine")
+ - **BBVTVLive**: [*bbvtv*](## "netrc machine")
+ - **BBVTVRecordings**: [*bbvtv*](## "netrc machine")
  - **BeatBumpPlaylist**
  - **BeatBumpVideo**
  - **Beatport**
@@ -165,8 +165,8 @@ # Supported sites
  - **BilibiliSpaceAudio**
  - **BilibiliSpacePlaylist**
  - **BilibiliSpaceVideo**
- - **BiliIntl**: [<abbr title="netrc machine"><em>biliintl</em></abbr>]
- - **biliIntl:series**: [<abbr title="netrc machine"><em>biliintl</em></abbr>]
+ - **BiliIntl**: [*biliintl*](## "netrc machine")
+ - **biliIntl:series**: [*biliintl*](## "netrc machine")
  - **BiliLive**
  - **BioBioChileTV**
  - **Biography**
@@ -232,7 +232,7 @@ # Supported sites
  - **cbssports:embed**
  - **CCMA**
  - **CCTV**: 央视网
- - **CDA**: [<abbr title="netrc machine"><em>cdapl</em></abbr>]
+ - **CDA**: [*cdapl*](## "netrc machine")
  - **Cellebrite**
  - **CeskaTelevize**
  - **CGTN**
@@ -286,8 +286,8 @@ # Supported sites
  - **CrooksAndLiars**
  - **CrowdBunker**
  - **CrowdBunkerChannel**
- - **crunchyroll**: [<abbr title="netrc machine"><em>crunchyroll</em></abbr>]
- - **crunchyroll:playlist**: [<abbr title="netrc machine"><em>crunchyroll</em></abbr>]
+ - **crunchyroll**: [*crunchyroll*](## "netrc machine")
+ - **crunchyroll:playlist**: [*crunchyroll*](## "netrc machine")
  - **CSpan**: C-SPAN
  - **CSpanCongress**
  - **CtsNews**: 華視新聞
@@ -295,18 +295,18 @@ # Supported sites
  - **CTVNews**
  - **cu.ntv.co.jp**: Nippon Television Network
  - **CultureUnplugged**
- - **curiositystream**: [<abbr title="netrc machine"><em>curiositystream</em></abbr>]
- - **curiositystream:collections**: [<abbr title="netrc machine"><em>curiositystream</em></abbr>]
- - **curiositystream:series**: [<abbr title="netrc machine"><em>curiositystream</em></abbr>]
+ - **curiositystream**: [*curiositystream*](## "netrc machine")
+ - **curiositystream:collections**: [*curiositystream*](## "netrc machine")
+ - **curiositystream:series**: [*curiositystream*](## "netrc machine")
  - **CWTV**
- - **Cybrary**: [<abbr title="netrc machine"><em>cybrary</em></abbr>]
- - **CybraryCourse**: [<abbr title="netrc machine"><em>cybrary</em></abbr>]
+ - **Cybrary**: [*cybrary*](## "netrc machine")
+ - **CybraryCourse**: [*cybrary*](## "netrc machine")
  - **Daftsex**
  - **DagelijkseKost**: dagelijksekost.een.be
  - **DailyMail**
- - **dailymotion**: [<abbr title="netrc machine"><em>dailymotion</em></abbr>]
- - **dailymotion:playlist**: [<abbr title="netrc machine"><em>dailymotion</em></abbr>]
- - **dailymotion:user**: [<abbr title="netrc machine"><em>dailymotion</em></abbr>]
+ - **dailymotion**: [*dailymotion*](## "netrc machine")
+ - **dailymotion:playlist**: [*dailymotion*](## "netrc machine")
+ - **dailymotion:user**: [*dailymotion*](## "netrc machine")
  - **DailyWire**
  - **DailyWirePodcast**
  - **damtomo:record**
@@ -328,7 +328,7 @@ # Supported sites
  - **DeuxMNews**
  - **DHM**: Filmarchiv - Deutsches Historisches Museum
  - **Digg**
- - **DigitalConcertHall**: [<abbr title="netrc machine"><em>digitalconcerthall</em></abbr>] DigitalConcertHall extractor
+ - **DigitalConcertHall**: [*digitalconcerthall*](## "netrc machine") DigitalConcertHall extractor
  - **DigitallySpeaking**
  - **Digiteka**
  - **Discovery**
@@ -351,7 +351,7 @@ # Supported sites
  - **DRBonanza**
  - **Drooble**
  - **Dropbox**
- - **Dropout**: [<abbr title="netrc machine"><em>dropout</em></abbr>]
+ - **Dropout**: [*dropout*](## "netrc machine")
  - **DropoutSeason**
  - **DrTuber**
  - **drtv**
@@ -373,9 +373,9 @@ # Supported sites
  - **egghead:lesson**: egghead.io lesson
  - **ehftv**
  - **eHow**
- - **EinsUndEinsTV**: [<abbr title="netrc machine"><em>1und1tv</em></abbr>]
- - **EinsUndEinsTVLive**: [<abbr title="netrc machine"><em>1und1tv</em></abbr>]
- - **EinsUndEinsTVRecordings**: [<abbr title="netrc machine"><em>1und1tv</em></abbr>]
+ - **EinsUndEinsTV**: [*1und1tv*](## "netrc machine")
+ - **EinsUndEinsTVLive**: [*1und1tv*](## "netrc machine")
+ - **EinsUndEinsTVRecordings**: [*1und1tv*](## "netrc machine")
  - **Einthusan**
  - **eitb.tv**
  - **EllenTube**
@@ -390,7 +390,7 @@ # Supported sites
  - **EpiconSeries**
  - **Epoch**
  - **Eporner**
- - **EroProfile**: [<abbr title="netrc machine"><em>eroprofile</em></abbr>]
+ - **EroProfile**: [*eroprofile*](## "netrc machine")
  - **EroProfile:album**
  - **ertflix**: ERTFLIX videos
  - **ertflix:codename**: ERTFLIX videos by codename
@@ -405,20 +405,20 @@ # Supported sites
  - **EuropeanTour**
  - **Eurosport**
  - **EUScreen**
- - **EWETV**: [<abbr title="netrc machine"><em>ewetv</em></abbr>]
- - **EWETVLive**: [<abbr title="netrc machine"><em>ewetv</em></abbr>]
- - **EWETVRecordings**: [<abbr title="netrc machine"><em>ewetv</em></abbr>]
+ - **EWETV**: [*ewetv*](## "netrc machine")
+ - **EWETVLive**: [*ewetv*](## "netrc machine")
+ - **EWETVRecordings**: [*ewetv*](## "netrc machine")
  - **ExpoTV**
  - **Expressen**
  - **ExtremeTube**
  - **EyedoTV**
- - **facebook**: [<abbr title="netrc machine"><em>facebook</em></abbr>]
+ - **facebook**: [*facebook*](## "netrc machine")
  - **facebook:reel**
  - **FacebookPluginsVideo**
- - **fancode:live**: [<abbr title="netrc machine"><em>fancode</em></abbr>]
- - **fancode:vod**: [<abbr title="netrc machine"><em>fancode</em></abbr>]
+ - **fancode:live**: [*fancode*](## "netrc machine")
+ - **fancode:vod**: [*fancode*](## "netrc machine")
  - **faz.net**
- - **fc2**: [<abbr title="netrc machine"><em>fc2</em></abbr>]
+ - **fc2**: [*fc2*](## "netrc machine")
  - **fc2:embed**
  - **fc2:live**
  - **Fczenit**
@@ -452,20 +452,20 @@ # Supported sites
  - **freespeech.org**
  - **freetv:series**
  - **FreeTvMovies**
- - **FrontendMasters**: [<abbr title="netrc machine"><em>frontendmasters</em></abbr>]
- - **FrontendMastersCourse**: [<abbr title="netrc machine"><em>frontendmasters</em></abbr>]
- - **FrontendMastersLesson**: [<abbr title="netrc machine"><em>frontendmasters</em></abbr>]
+ - **FrontendMasters**: [*frontendmasters*](## "netrc machine")
+ - **FrontendMastersCourse**: [*frontendmasters*](## "netrc machine")
+ - **FrontendMastersLesson**: [*frontendmasters*](## "netrc machine")
  - **FujiTVFODPlus7**
- - **Funimation**: [<abbr title="netrc machine"><em>funimation</em></abbr>]
- - **funimation:page**: [<abbr title="netrc machine"><em>funimation</em></abbr>]
- - **funimation:show**: [<abbr title="netrc machine"><em>funimation</em></abbr>]
+ - **Funimation**: [*funimation*](## "netrc machine")
+ - **funimation:page**: [*funimation*](## "netrc machine")
+ - **funimation:show**: [*funimation*](## "netrc machine")
  - **Funk**
  - **Fusion**
  - **Fux**
  - **FuyinTV**
  - **Gab**
  - **GabTV**
- - **Gaia**: [<abbr title="netrc machine"><em>gaia</em></abbr>]
+ - **Gaia**: [*gaia*](## "netrc machine")
  - **GameInformer**
  - **GameJolt**
  - **GameJoltCommunity**
@@ -477,9 +477,9 @@ # Supported sites
  - **GameStar**
  - **Gaskrank**
  - **Gazeta**
- - **GDCVault**: [<abbr title="netrc machine"><em>gdcvault</em></abbr>]
+ - **GDCVault**: [*gdcvault*](## "netrc machine")
  - **GediDigital**
- - **gem.cbc.ca**: [<abbr title="netrc machine"><em>cbcgem</em></abbr>]
+ - **gem.cbc.ca**: [*cbcgem*](## "netrc machine")
  - **gem.cbc.ca:live**
  - **gem.cbc.ca:playlist**
  - **Genius**
@@ -489,11 +489,11 @@ # Supported sites
  - **Gfycat**
  - **GiantBomb**
  - **Giga**
- - **GlattvisionTV**: [<abbr title="netrc machine"><em>glattvisiontv</em></abbr>]
- - **GlattvisionTVLive**: [<abbr title="netrc machine"><em>glattvisiontv</em></abbr>]
- - **GlattvisionTVRecordings**: [<abbr title="netrc machine"><em>glattvisiontv</em></abbr>]
+ - **GlattvisionTV**: [*glattvisiontv*](## "netrc machine")
+ - **GlattvisionTVLive**: [*glattvisiontv*](## "netrc machine")
+ - **GlattvisionTVRecordings**: [*glattvisiontv*](## "netrc machine")
  - **Glide**: Glide mobile video messages (glide.me)
- - **Globo**: [<abbr title="netrc machine"><em>globo</em></abbr>]
+ - **Globo**: [*globo*](## "netrc machine")
  - **GloboArticle**
  - **glomex**: Glomex videos
  - **glomex:embed**: Glomex embedded videos
@@ -507,7 +507,7 @@ # Supported sites
  - **google:​podcasts:feed**
  - **GoogleDrive**
  - **GoogleDrive:Folder**
- - **GoPlay**: [<abbr title="netrc machine"><em>goplay</em></abbr>]
+ - **GoPlay**: [*goplay*](## "netrc machine")
  - **GoPro**
  - **Goshgay**
  - **GoToStage**
@@ -527,7 +527,7 @@ # Supported sites
  - **hgtv.com:show**
  - **HGTVDe**
  - **HGTVUsa**
- - **HiDive**: [<abbr title="netrc machine"><em>hidive</em></abbr>]
+ - **HiDive**: [*hidive*](## "netrc machine")
  - **HistoricFilms**
  - **history:player**
  - **history:topic**: History.com Topic
@@ -544,8 +544,8 @@ # Supported sites
  - **Howcast**
  - **HowStuffWorks**
  - **hrfernsehen**
- - **HRTi**: [<abbr title="netrc machine"><em>hrti</em></abbr>]
- - **HRTiPlaylist**: [<abbr title="netrc machine"><em>hrti</em></abbr>]
+ - **HRTi**: [*hrti*](## "netrc machine")
+ - **HRTiPlaylist**: [*hrti*](## "netrc machine")
  - **HSEProduct**
  - **HSEShow**
  - **html5**
@@ -575,19 +575,19 @@ # Supported sites
  - **Inc**
  - **IndavideoEmbed**
  - **InfoQ**
- - **Instagram**: [<abbr title="netrc machine"><em>instagram</em></abbr>]
- - **instagram:story**: [<abbr title="netrc machine"><em>instagram</em></abbr>]
- - **instagram:tag**: [<abbr title="netrc machine"><em>instagram</em></abbr>] Instagram hashtag search URLs
- - **instagram:user**: [<abbr title="netrc machine"><em>instagram</em></abbr>] Instagram user profile
+ - **Instagram**: [*instagram*](## "netrc machine")
+ - **instagram:story**: [*instagram*](## "netrc machine")
+ - **instagram:tag**: [*instagram*](## "netrc machine") Instagram hashtag search URLs
+ - **instagram:user**: [*instagram*](## "netrc machine") Instagram user profile
  - **InstagramIOS**: IOS instagram:// URL
  - **Internazionale**
  - **InternetVideoArchive**
  - **InvestigationDiscovery**
- - **IPrima**: [<abbr title="netrc machine"><em>iprima</em></abbr>]
+ - **IPrima**: [*iprima*](## "netrc machine")
  - **IPrimaCNN**
  - **iq.com**: International version of iQiyi
  - **iq.com:album**
- - **iqiyi**: [<abbr title="netrc machine"><em>iqiyi</em></abbr>] 爱奇艺
+ - **iqiyi**: [*iqiyi*](## "netrc machine") 爱奇艺
  - **IslamChannel**
  - **IslamChannelSeries**
  - **IsraelNationalNews**
@@ -660,9 +660,9 @@ # Supported sites
  - **LcpPlay**
  - **Le**: 乐视网
  - **Lecture2Go**
- - **Lecturio**: [<abbr title="netrc machine"><em>lecturio</em></abbr>]
- - **LecturioCourse**: [<abbr title="netrc machine"><em>lecturio</em></abbr>]
- - **LecturioDeCourse**: [<abbr title="netrc machine"><em>lecturio</em></abbr>]
+ - **Lecturio**: [*lecturio*](## "netrc machine")
+ - **LecturioCourse**: [*lecturio*](## "netrc machine")
+ - **LecturioDeCourse**: [*lecturio*](## "netrc machine")
  - **LEGO**
  - **Lemonde**
  - **Lenta**
@@ -678,10 +678,10 @@ # Supported sites
  - **limelight:channel_list**
  - **LineLive**
  - **LineLiveChannel**
- - **LinkedIn**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
- - **linkedin:learning**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
- - **linkedin:​learning:course**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
- - **LinuxAcademy**: [<abbr title="netrc machine"><em>linuxacademy</em></abbr>]
+ - **LinkedIn**: [*linkedin*](## "netrc machine")
+ - **linkedin:learning**: [*linkedin*](## "netrc machine")
+ - **linkedin:​learning:course**: [*linkedin*](## "netrc machine")
+ - **LinuxAcademy**: [*linuxacademy*](## "netrc machine")
  - **Liputan6**
  - **ListenNotes**
  - **LiTV**
@@ -696,8 +696,8 @@ # Supported sites
  - **LoveHomePorn**
  - **LRTStream**
  - **LRTVOD**
- - **lynda**: [<abbr title="netrc machine"><em>lynda</em></abbr>] lynda.com videos
- - **lynda:course**: [<abbr title="netrc machine"><em>lynda</em></abbr>] lynda.com online courses
+ - **lynda**: [*lynda*](## "netrc machine") lynda.com videos
+ - **lynda:course**: [*lynda*](## "netrc machine") lynda.com online courses
  - **m6**
  - **MagentaMusik360**
  - **mailru**: Видео@Mail.Ru
@@ -767,13 +767,13 @@ # Supported sites
  - **mixcloud:user**
  - **MLB**
  - **MLBArticle**
- - **MLBTV**: [<abbr title="netrc machine"><em>mlb</em></abbr>]
+ - **MLBTV**: [*mlb*](## "netrc machine")
  - **MLBVideo**
  - **MLSSoccer**
  - **Mnet**
- - **MNetTV**: [<abbr title="netrc machine"><em>mnettv</em></abbr>]
- - **MNetTVLive**: [<abbr title="netrc machine"><em>mnettv</em></abbr>]
- - **MNetTVRecordings**: [<abbr title="netrc machine"><em>mnettv</em></abbr>]
+ - **MNetTV**: [*mnettv*](## "netrc machine")
+ - **MNetTVLive**: [*mnettv*](## "netrc machine")
+ - **MNetTVRecordings**: [*mnettv*](## "netrc machine")
  - **MochaVideo**
  - **MoeVideo**: LetitBit video services: moevideo.net, playreplay.net and videochart.net
  - **Mofosex**
@@ -852,9 +852,9 @@ # Supported sites
  - **ndr:embed**
  - **ndr:​embed:base**
  - **NDTV**
- - **Nebula**: [<abbr title="netrc machine"><em>watchnebula</em></abbr>]
- - **nebula:channel**: [<abbr title="netrc machine"><em>watchnebula</em></abbr>]
- - **nebula:subscriptions**: [<abbr title="netrc machine"><em>watchnebula</em></abbr>]
+ - **Nebula**: [*watchnebula*](## "netrc machine")
+ - **nebula:channel**: [*watchnebula*](## "netrc machine")
+ - **nebula:subscriptions**: [*watchnebula*](## "netrc machine")
  - **NerdCubedFeed**
  - **netease:album**: 网易云音乐 - 专辑
  - **netease:djradio**: 网易云音乐 - 电台
@@ -863,9 +863,9 @@ # Supported sites
  - **netease:program**: 网易云音乐 - 电台节目
  - **netease:singer**: 网易云音乐 - 歌手
  - **netease:song**: 网易云音乐
- - **NetPlusTV**: [<abbr title="netrc machine"><em>netplus</em></abbr>]
- - **NetPlusTVLive**: [<abbr title="netrc machine"><em>netplus</em></abbr>]
- - **NetPlusTVRecordings**: [<abbr title="netrc machine"><em>netplus</em></abbr>]
+ - **NetPlusTV**: [*netplus*](## "netrc machine")
+ - **NetPlusTVLive**: [*netplus*](## "netrc machine")
+ - **NetPlusTVRecordings**: [*netplus*](## "netrc machine")
  - **Netverse**
  - **NetversePlaylist**
  - **NetverseSearch**: "netsearch:" prefix
@@ -898,7 +898,7 @@ # Supported sites
  - **nickelodeon:br**
  - **nickelodeonru**
  - **nicknight**
- - **niconico**: [<abbr title="netrc machine"><em>niconico</em></abbr>] ニコニコ動画
+ - **niconico**: [*niconico*](## "netrc machine") ニコニコ動画
  - **niconico:history**: NicoNico user history or likes. Requires cookies.
  - **niconico:playlist**
  - **niconico:series**
@@ -911,7 +911,7 @@ # Supported sites
  - **Nitter**
  - **njoy**: N-JOY
  - **njoy:embed**
- - **NJPWWorld**: [<abbr title="netrc machine"><em>njpwworld</em></abbr>] 新日本プロレスワールド
+ - **NJPWWorld**: [*njpwworld*](## "netrc machine") 新日本プロレスワールド
  - **NobelPrize**
  - **NoicePodcast**
  - **NonkTube**
@@ -980,11 +980,11 @@ # Supported sites
  - **orf:iptv**: iptv.ORF.at
  - **orf:radio**
  - **orf:tvthek**: ORF TVthek
- - **OsnatelTV**: [<abbr title="netrc machine"><em>osnateltv</em></abbr>]
- - **OsnatelTVLive**: [<abbr title="netrc machine"><em>osnateltv</em></abbr>]
- - **OsnatelTVRecordings**: [<abbr title="netrc machine"><em>osnateltv</em></abbr>]
+ - **OsnatelTV**: [*osnateltv*](## "netrc machine")
+ - **OsnatelTVLive**: [*osnateltv*](## "netrc machine")
+ - **OsnatelTVRecordings**: [*osnateltv*](## "netrc machine")
  - **OutsideTV**
- - **PacktPub**: [<abbr title="netrc machine"><em>packtpub</em></abbr>]
+ - **PacktPub**: [*packtpub*](## "netrc machine")
  - **PacktPubCourse**
  - **PalcoMP3:artist**
  - **PalcoMP3:song**
@@ -1007,7 +1007,7 @@ # Supported sites
  - **peer.tv**
  - **PeerTube**
  - **PeerTube:Playlist**
- - **peloton**: [<abbr title="netrc machine"><em>peloton</em></abbr>]
+ - **peloton**: [*peloton*](## "netrc machine")
  - **peloton:live**: Peloton Live
  - **People**
  - **PerformGroup**
@@ -1016,7 +1016,7 @@ # Supported sites
  - **PhilharmonieDeParis**: Philharmonie de Paris
  - **phoenix.de**
  - **Photobucket**
- - **Piapro**: [<abbr title="netrc machine"><em>piapro</em></abbr>]
+ - **Piapro**: [*piapro*](## "netrc machine")
  - **Picarto**
  - **PicartoVod**
  - **Piksel**
@@ -1027,11 +1027,11 @@ # Supported sites
  - **pixiv:​sketch:user**
  - **Pladform**
  - **PlanetMarathi**
- - **Platzi**: [<abbr title="netrc machine"><em>platzi</em></abbr>]
- - **PlatziCourse**: [<abbr title="netrc machine"><em>platzi</em></abbr>]
+ - **Platzi**: [*platzi*](## "netrc machine")
+ - **PlatziCourse**: [*platzi*](## "netrc machine")
  - **play.fm**
  - **player.sky.it**
- - **PlayPlusTV**: [<abbr title="netrc machine"><em>playplustv</em></abbr>]
+ - **PlayPlusTV**: [*playplustv*](## "netrc machine")
  - **PlayStuff**
  - **PlaysTV**
  - **PlaySuisse**
@@ -1039,7 +1039,7 @@ # Supported sites
  - **Playvid**
  - **PlayVids**
  - **Playwire**
- - **pluralsight**: [<abbr title="netrc machine"><em>pluralsight</em></abbr>]
+ - **pluralsight**: [*pluralsight*](## "netrc machine")
  - **pluralsight:course**
  - **PlutoTV**
  - **PodbayFM**
@@ -1048,8 +1048,8 @@ # Supported sites
  - **podomatic**
  - **Pokemon**
  - **PokemonWatch**
- - **PokerGo**: [<abbr title="netrc machine"><em>pokergo</em></abbr>]
- - **PokerGoCollection**: [<abbr title="netrc machine"><em>pokergo</em></abbr>]
+ - **PokerGo**: [*pokergo*](## "netrc machine")
+ - **PokerGoCollection**: [*pokergo*](## "netrc machine")
  - **PolsatGo**
  - **PolskieRadio**
  - **polskieradio:audition**
@@ -1066,11 +1066,11 @@ # Supported sites
  - **Pornez**
  - **PornFlip**
  - **PornHd**
- - **PornHub**: [<abbr title="netrc machine"><em>pornhub</em></abbr>] PornHub and Thumbzilla
- - **PornHubPagedVideoList**: [<abbr title="netrc machine"><em>pornhub</em></abbr>]
- - **PornHubPlaylist**: [<abbr title="netrc machine"><em>pornhub</em></abbr>]
- - **PornHubUser**: [<abbr title="netrc machine"><em>pornhub</em></abbr>]
- - **PornHubUserVideosUpload**: [<abbr title="netrc machine"><em>pornhub</em></abbr>]
+ - **PornHub**: [*pornhub*](## "netrc machine") PornHub and Thumbzilla
+ - **PornHubPagedVideoList**: [*pornhub*](## "netrc machine")
+ - **PornHubPlaylist**: [*pornhub*](## "netrc machine")
+ - **PornHubUser**: [*pornhub*](## "netrc machine")
+ - **PornHubUserVideosUpload**: [*pornhub*](## "netrc machine")
  - **Pornotube**
  - **PornoVoisines**
  - **PornoXO**
@@ -1098,9 +1098,9 @@ # Supported sites
  - **qqmusic:playlist**: QQ音乐 - 歌单
  - **qqmusic:singer**: QQ音乐 - 歌手
  - **qqmusic:toplist**: QQ音乐 - 排行榜
- - **QuantumTV**: [<abbr title="netrc machine"><em>quantumtv</em></abbr>]
- - **QuantumTVLive**: [<abbr title="netrc machine"><em>quantumtv</em></abbr>]
- - **QuantumTVRecordings**: [<abbr title="netrc machine"><em>quantumtv</em></abbr>]
+ - **QuantumTV**: [*quantumtv*](## "netrc machine")
+ - **QuantumTVLive**: [*quantumtv*](## "netrc machine")
+ - **QuantumTVRecordings**: [*quantumtv*](## "netrc machine")
  - **Qub**
  - **R7**
  - **R7Article**
@@ -1157,16 +1157,16 @@ # Supported sites
  - **RICE**
  - **RMCDecouverte**
  - **RockstarGames**
- - **Rokfin**: [<abbr title="netrc machine"><em>rokfin</em></abbr>]
+ - **Rokfin**: [*rokfin*](## "netrc machine")
  - **rokfin:channel**: Rokfin Channels
  - **rokfin:search**: Rokfin Search; "rkfnsearch:" prefix
  - **rokfin:stack**: Rokfin Stacks
- - **RoosterTeeth**: [<abbr title="netrc machine"><em>roosterteeth</em></abbr>]
- - **RoosterTeethSeries**: [<abbr title="netrc machine"><em>roosterteeth</em></abbr>]
+ - **RoosterTeeth**: [*roosterteeth*](## "netrc machine")
+ - **RoosterTeethSeries**: [*roosterteeth*](## "netrc machine")
  - **RottenTomatoes**
  - **Rozhlas**
  - **RozhlasVltava**
- - **RTBF**: [<abbr title="netrc machine"><em>rtbf</em></abbr>]
+ - **RTBF**: [*rtbf*](## "netrc machine")
  - **RTDocumentry**
  - **RTDocumentryPlaylist**
  - **rte**: Raidió Teilifís Éireann TV
@@ -1208,16 +1208,16 @@ # Supported sites
  - **Ruutu**
  - **Ruv**
  - **ruv.is:spila**
- - **safari**: [<abbr title="netrc machine"><em>safari</em></abbr>] safaribooksonline.com online video
- - **safari:api**: [<abbr title="netrc machine"><em>safari</em></abbr>]
- - **safari:course**: [<abbr title="netrc machine"><em>safari</em></abbr>] safaribooksonline.com online courses
+ - **safari**: [*safari*](## "netrc machine") safaribooksonline.com online video
+ - **safari:api**: [*safari*](## "netrc machine")
+ - **safari:course**: [*safari*](## "netrc machine") safaribooksonline.com online courses
  - **Saitosan**
- - **SAKTV**: [<abbr title="netrc machine"><em>saktv</em></abbr>]
- - **SAKTVLive**: [<abbr title="netrc machine"><em>saktv</em></abbr>]
- - **SAKTVRecordings**: [<abbr title="netrc machine"><em>saktv</em></abbr>]
- - **SaltTV**: [<abbr title="netrc machine"><em>salttv</em></abbr>]
- - **SaltTVLive**: [<abbr title="netrc machine"><em>salttv</em></abbr>]
- - **SaltTVRecordings**: [<abbr title="netrc machine"><em>salttv</em></abbr>]
+ - **SAKTV**: [*saktv*](## "netrc machine")
+ - **SAKTVLive**: [*saktv*](## "netrc machine")
+ - **SAKTVRecordings**: [*saktv*](## "netrc machine")
+ - **SaltTV**: [*salttv*](## "netrc machine")
+ - **SaltTVLive**: [*salttv*](## "netrc machine")
+ - **SaltTVRecordings**: [*salttv*](## "netrc machine")
  - **SampleFocus**
  - **Sangiin**: 参議院インターネット審議中継 (archive)
  - **Sapo**: SAPO Vídeos
@@ -1233,8 +1233,8 @@ # Supported sites
  - **ScrippsNetworks**
  - **scrippsnetworks:watch**
  - **Scrolller**
- - **SCTE**: [<abbr title="netrc machine"><em>scte</em></abbr>]
- - **SCTECourse**: [<abbr title="netrc machine"><em>scte</em></abbr>]
+ - **SCTE**: [*scte*](## "netrc machine")
+ - **SCTECourse**: [*scte*](## "netrc machine")
  - **Seeker**
  - **SenateGov**
  - **SenateISVP**
@@ -1243,7 +1243,7 @@ # Supported sites
  - **Sexu**
  - **SeznamZpravy**
  - **SeznamZpravyArticle**
- - **Shahid**: [<abbr title="netrc machine"><em>shahid</em></abbr>]
+ - **Shahid**: [*shahid*](## "netrc machine")
  - **ShahidShow**
  - **Shared**: shared.sx
  - **ShareVideosEmbed**
@@ -1273,16 +1273,16 @@ # Supported sites
  - **Smotrim**
  - **Snotr**
  - **Sohu**
- - **SonyLIV**: [<abbr title="netrc machine"><em>sonyliv</em></abbr>]
+ - **SonyLIV**: [*sonyliv*](## "netrc machine")
  - **SonyLIVSeries**
- - **soundcloud**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:playlist**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:related**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:search**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>] Soundcloud search; "scsearch:" prefix
- - **soundcloud:set**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:trackstation**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:user**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
- - **soundcloud:​user:permalink**: [<abbr title="netrc machine"><em>soundcloud</em></abbr>]
+ - **soundcloud**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:playlist**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:related**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:search**: [*soundcloud*](## "netrc machine") Soundcloud search; "scsearch:" prefix
+ - **soundcloud:set**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:trackstation**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:user**: [*soundcloud*](## "netrc machine")
+ - **soundcloud:​user:permalink**: [*soundcloud*](## "netrc machine")
  - **SoundcloudEmbed**
  - **soundgasm**
  - **soundgasm:profile**
@@ -1349,13 +1349,13 @@ # Supported sites
  - **Tass**
  - **TBS**
  - **TDSLifeway**
- - **Teachable**: [<abbr title="netrc machine"><em>teachable</em></abbr>]
- - **TeachableCourse**: [<abbr title="netrc machine"><em>teachable</em></abbr>]
+ - **Teachable**: [*teachable*](## "netrc machine")
+ - **TeachableCourse**: [*teachable*](## "netrc machine")
  - **teachertube**: teachertube.com videos
  - **teachertube:​user:collection**: teachertube.com user and collection videos
  - **TeachingChannel**
  - **Teamcoco**
- - **TeamTreeHouse**: [<abbr title="netrc machine"><em>teamtreehouse</em></abbr>]
+ - **TeamTreeHouse**: [*teamtreehouse*](## "netrc machine")
  - **TechTalks**
  - **techtv.mit.edu**
  - **TedEmbed**
@@ -1378,8 +1378,8 @@ # Supported sites
  - **TeleTask**
  - **Telewebion**
  - **Tempo**
- - **TennisTV**: [<abbr title="netrc machine"><em>tennistv</em></abbr>]
- - **TenPlay**: [<abbr title="netrc machine"><em>10play</em></abbr>]
+ - **TennisTV**: [*tennistv*](## "netrc machine")
+ - **TenPlay**: [*10play*](## "netrc machine")
  - **TF1**
  - **TFO**
  - **TheHoleTv**
@@ -1417,13 +1417,13 @@ # Supported sites
  - **tokfm:audition**
  - **tokfm:podcast**
  - **ToonGoggles**
- - **tou.tv**: [<abbr title="netrc machine"><em>toutv</em></abbr>]
+ - **tou.tv**: [*toutv*](## "netrc machine")
  - **Toypics**: Toypics video
  - **ToypicsUser**: Toypics user profile
  - **TrailerAddict**: (**Currently broken**)
  - **TravelChannel**
- - **Triller**: [<abbr title="netrc machine"><em>triller</em></abbr>]
- - **TrillerUser**: [<abbr title="netrc machine"><em>triller</em></abbr>]
+ - **Triller**: [*triller*](## "netrc machine")
+ - **TrillerUser**: [*triller*](## "netrc machine")
  - **Trilulilu**
  - **Trovo**
  - **TrovoChannelClip**: All Clips of a trovo.live channel; "trovoclip:" prefix
@@ -1435,11 +1435,11 @@ # Supported sites
  - **Truth**
  - **TruTV**
  - **Tube8**
- - **TubeTuGraz**: [<abbr title="netrc machine"><em>tubetugraz</em></abbr>] tube.tugraz.at
- - **TubeTuGrazSeries**: [<abbr title="netrc machine"><em>tubetugraz</em></abbr>]
- - **TubiTv**: [<abbr title="netrc machine"><em>tubitv</em></abbr>]
+ - **TubeTuGraz**: [*tubetugraz*](## "netrc machine") tube.tugraz.at
+ - **TubeTuGrazSeries**: [*tubetugraz*](## "netrc machine")
+ - **TubiTv**: [*tubitv*](## "netrc machine")
  - **TubiTvShow**
- - **Tumblr**: [<abbr title="netrc machine"><em>tumblr</em></abbr>]
+ - **Tumblr**: [*tumblr*](## "netrc machine")
  - **tunein:clip**
  - **tunein:program**
  - **tunein:station**
@@ -1489,13 +1489,13 @@ # Supported sites
  - **TwitCasting**
  - **TwitCastingLive**
  - **TwitCastingUser**
- - **twitch:clips**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **twitch:stream**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **twitch:vod**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **TwitchCollection**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **TwitchVideos**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **TwitchVideosClips**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
- - **TwitchVideosCollections**: [<abbr title="netrc machine"><em>twitch</em></abbr>]
+ - **twitch:clips**: [*twitch*](## "netrc machine")
+ - **twitch:stream**: [*twitch*](## "netrc machine")
+ - **twitch:vod**: [*twitch*](## "netrc machine")
+ - **TwitchCollection**: [*twitch*](## "netrc machine")
+ - **TwitchVideos**: [*twitch*](## "netrc machine")
+ - **TwitchVideosClips**: [*twitch*](## "netrc machine")
+ - **TwitchVideosCollections**: [*twitch*](## "netrc machine")
  - **twitter**
  - **twitter:amplify**
  - **twitter:broadcast**
@@ -1503,11 +1503,11 @@ # Supported sites
  - **twitter:shortener**
  - **twitter:spaces**
  - **Txxx**
- - **udemy**: [<abbr title="netrc machine"><em>udemy</em></abbr>]
- - **udemy:course**: [<abbr title="netrc machine"><em>udemy</em></abbr>]
+ - **udemy**: [*udemy*](## "netrc machine")
+ - **udemy:course**: [*udemy*](## "netrc machine")
  - **UDNEmbed**: 聯合影音
- - **UFCArabia**: [<abbr title="netrc machine"><em>ufcarabia</em></abbr>]
- - **UFCTV**: [<abbr title="netrc machine"><em>ufctv</em></abbr>]
+ - **UFCArabia**: [*ufcarabia*](## "netrc machine")
+ - **UFCTV**: [*ufctv*](## "netrc machine")
  - **ukcolumn**
  - **UKTVPlay**
  - **umg:de**: Universal Music Deutschland
@@ -1537,7 +1537,7 @@ # Supported sites
  - **VevoPlaylist**
  - **VGTV**: VGTV, BTTV, FTV, Aftenposten and Aftonbladet
  - **vh1.com**
- - **vhx:embed**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
+ - **vhx:embed**: [*vimeo*](## "netrc machine")
  - **Viafree**
  - **vice**
  - **vice:article**
@@ -1560,25 +1560,25 @@ # Supported sites
  - **videomore:season**
  - **videomore:video**
  - **VideoPress**
- - **Vidio**: [<abbr title="netrc machine"><em>vidio</em></abbr>]
- - **VidioLive**: [<abbr title="netrc machine"><em>vidio</em></abbr>]
- - **VidioPremier**: [<abbr title="netrc machine"><em>vidio</em></abbr>]
+ - **Vidio**: [*vidio*](## "netrc machine")
+ - **VidioLive**: [*vidio*](## "netrc machine")
+ - **VidioPremier**: [*vidio*](## "netrc machine")
  - **VidLii**
  - **viewlift**
  - **viewlift:embed**
  - **Viidea**
- - **viki**: [<abbr title="netrc machine"><em>viki</em></abbr>]
- - **viki:channel**: [<abbr title="netrc machine"><em>viki</em></abbr>]
- - **vimeo**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:album**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:channel**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:group**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:likes**: [<abbr title="netrc machine"><em>vimeo</em></abbr>] Vimeo user likes
- - **vimeo:ondemand**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:pro**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:review**: [<abbr title="netrc machine"><em>vimeo</em></abbr>] Review pages on vimeo
- - **vimeo:user**: [<abbr title="netrc machine"><em>vimeo</em></abbr>]
- - **vimeo:watchlater**: [<abbr title="netrc machine"><em>vimeo</em></abbr>] Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)
+ - **viki**: [*viki*](## "netrc machine")
+ - **viki:channel**: [*viki*](## "netrc machine")
+ - **vimeo**: [*vimeo*](## "netrc machine")
+ - **vimeo:album**: [*vimeo*](## "netrc machine")
+ - **vimeo:channel**: [*vimeo*](## "netrc machine")
+ - **vimeo:group**: [*vimeo*](## "netrc machine")
+ - **vimeo:likes**: [*vimeo*](## "netrc machine") Vimeo user likes
+ - **vimeo:ondemand**: [*vimeo*](## "netrc machine")
+ - **vimeo:pro**: [*vimeo*](## "netrc machine")
+ - **vimeo:review**: [*vimeo*](## "netrc machine") Review pages on vimeo
+ - **vimeo:user**: [*vimeo*](## "netrc machine")
+ - **vimeo:watchlater**: [*vimeo*](## "netrc machine") Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)
  - **Vimm:recording**
  - **Vimm:stream**
  - **ViMP**
@@ -1588,13 +1588,13 @@ # Supported sites
  - **vine:user**
  - **Viqeo**
  - **Viu**
- - **viu:ott**: [<abbr title="netrc machine"><em>viu</em></abbr>]
+ - **viu:ott**: [*viu*](## "netrc machine")
  - **viu:playlist**
  - **ViuOTTIndonesia**
  - **Vivo**: vivo.sx
- - **vk**: [<abbr title="netrc machine"><em>vk</em></abbr>] VK
- - **vk:uservideos**: [<abbr title="netrc machine"><em>vk</em></abbr>] VK - User's Videos
- - **vk:wallpost**: [<abbr title="netrc machine"><em>vk</em></abbr>]
+ - **vk**: [*vk*](## "netrc machine") VK
+ - **vk:uservideos**: [*vk*](## "netrc machine") VK - User's Videos
+ - **vk:wallpost**: [*vk*](## "netrc machine")
  - **vm.tiktok**
  - **Vocaroo**
  - **Vodlocker**
@@ -1613,14 +1613,14 @@ # Supported sites
  - **vqq:video**
  - **Vrak**
  - **VRT**: VRT NWS, Flanders News, Flandern Info and Sporza
- - **VrtNU**: [<abbr title="netrc machine"><em>vrtnu</em></abbr>] VrtNU.be
- - **vrv**: [<abbr title="netrc machine"><em>vrv</em></abbr>]
+ - **VrtNU**: [*vrtnu*](## "netrc machine") VrtNU.be
+ - **vrv**: [*vrv*](## "netrc machine")
  - **vrv:series**
  - **VShare**
  - **VTM**
- - **VTXTV**: [<abbr title="netrc machine"><em>vtxtv</em></abbr>]
- - **VTXTVLive**: [<abbr title="netrc machine"><em>vtxtv</em></abbr>]
- - **VTXTVRecordings**: [<abbr title="netrc machine"><em>vtxtv</em></abbr>]
+ - **VTXTV**: [*vtxtv*](## "netrc machine")
+ - **VTXTVLive**: [*vtxtv*](## "netrc machine")
+ - **VTXTVRecordings**: [*vtxtv*](## "netrc machine")
  - **VuClip**
  - **Vupload**
  - **VVVVID**
@@ -1629,9 +1629,9 @@ # Supported sites
  - **Vzaar**
  - **Wakanim**
  - **Walla**
- - **WalyTV**: [<abbr title="netrc machine"><em>walytv</em></abbr>]
- - **WalyTVLive**: [<abbr title="netrc machine"><em>walytv</em></abbr>]
- - **WalyTVRecordings**: [<abbr title="netrc machine"><em>walytv</em></abbr>]
+ - **WalyTV**: [*walytv*](## "netrc machine")
+ - **WalyTVLive**: [*walytv*](## "netrc machine")
+ - **WalyTVRecordings**: [*walytv*](## "netrc machine")
  - **wasdtv:clip**
  - **wasdtv:record**
  - **wasdtv:stream**
@@ -1743,13 +1743,13 @@ # Supported sites
  - **YoutubeLivestreamEmbed**: YouTube livestream embeds
  - **YoutubeYtBe**: youtu.be
  - **Zapiks**
- - **Zattoo**: [<abbr title="netrc machine"><em>zattoo</em></abbr>]
- - **ZattooLive**: [<abbr title="netrc machine"><em>zattoo</em></abbr>]
- - **ZattooMovies**: [<abbr title="netrc machine"><em>zattoo</em></abbr>]
- - **ZattooRecordings**: [<abbr title="netrc machine"><em>zattoo</em></abbr>]
+ - **Zattoo**: [*zattoo*](## "netrc machine")
+ - **ZattooLive**: [*zattoo*](## "netrc machine")
+ - **ZattooMovies**: [*zattoo*](## "netrc machine")
+ - **ZattooRecordings**: [*zattoo*](## "netrc machine")
  - **ZDF**
  - **ZDFChannel**
- - **Zee5**: [<abbr title="netrc machine"><em>zee5</em></abbr>]
+ - **Zee5**: [*zee5*](## "netrc machine")
  - **zee5:series**
  - **ZeeNews**
  - **ZenYandex**
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index d6c5ce769..00846cd7e 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -614,7 +614,7 @@ def __init__(self, params=None, auto_init=True):
                    '\n                    You will no longer receive updates on this version')
             if current_version < MIN_SUPPORTED:
                 msg = 'Python version %d.%d is no longer supported'
-            self.deprecation_warning(
+            self.deprecated_feature(
                 f'{msg}! Please update to Python %d.%d or above' % (*current_version, *MIN_RECOMMENDED))
 
         if self.params.get('allow_unplayable_formats'):
diff --git a/yt_dlp/__pyinstaller/hook-yt_dlp.py b/yt_dlp/__pyinstaller/hook-yt_dlp.py
index 057cfef2f..63dcdffe0 100644
--- a/yt_dlp/__pyinstaller/hook-yt_dlp.py
+++ b/yt_dlp/__pyinstaller/hook-yt_dlp.py
@@ -1,30 +1,8 @@
-import ast
-import os
 import sys
-from pathlib import Path
 
 from PyInstaller.utils.hooks import collect_submodules
 
 
-def find_attribute_accesses(node, name, path=()):
-    if isinstance(node, ast.Attribute):
-        path = [*path, node.attr]
-        if isinstance(node.value, ast.Name) and node.value.id == name:
-            yield path[::-1]
-    for child in ast.iter_child_nodes(node):
-        yield from find_attribute_accesses(child, name, path)
-
-
-def collect_used_submodules(name, level):
-    for dirpath, _, filenames in os.walk(Path(__file__).parent.parent):
-        for filename in filenames:
-            if not filename.endswith('.py'):
-                continue
-            with open(Path(dirpath) / filename, encoding='utf8') as f:
-                for submodule in find_attribute_accesses(ast.parse(f.read()), name):
-                    yield '.'.join(submodule[:level])
-
-
 def pycryptodome_module():
     try:
         import Cryptodome  # noqa: F401
@@ -41,12 +19,8 @@ def pycryptodome_module():
 
 def get_hidden_imports():
     yield 'yt_dlp.compat._legacy'
+    yield pycryptodome_module()
     yield from collect_submodules('websockets')
-
-    crypto = pycryptodome_module()
-    for sm in set(collect_used_submodules('Cryptodome', 2)):
-        yield f'{crypto}.{sm}'
-
     # These are auto-detected, but explicitly add them just in case
     yield from ('mutagen', 'brotli', 'certifi')
 
diff --git a/yt_dlp/aes.py b/yt_dlp/aes.py
index deff0a2b3..b3a383cd9 100644
--- a/yt_dlp/aes.py
+++ b/yt_dlp/aes.py
@@ -5,14 +5,14 @@
 from .dependencies import Cryptodome
 from .utils import bytes_to_intlist, intlist_to_bytes
 
-if Cryptodome:
+if Cryptodome.AES:
     def aes_cbc_decrypt_bytes(data, key, iv):
         """ Decrypt bytes with AES-CBC using pycryptodome """
-        return Cryptodome.Cipher.AES.new(key, Cryptodome.Cipher.AES.MODE_CBC, iv).decrypt(data)
+        return Cryptodome.AES.new(key, Cryptodome.AES.MODE_CBC, iv).decrypt(data)
 
     def aes_gcm_decrypt_and_verify_bytes(data, key, tag, nonce):
         """ Decrypt bytes with AES-GCM using pycryptodome """
-        return Cryptodome.Cipher.AES.new(key, Cryptodome.Cipher.AES.MODE_GCM, nonce).decrypt_and_verify(data, tag)
+        return Cryptodome.AES.new(key, Cryptodome.AES.MODE_GCM, nonce).decrypt_and_verify(data, tag)
 
 else:
     def aes_cbc_decrypt_bytes(data, key, iv):
diff --git a/yt_dlp/compat/_legacy.py b/yt_dlp/compat/_legacy.py
index 84d749209..83bf869a8 100644
--- a/yt_dlp/compat/_legacy.py
+++ b/yt_dlp/compat/_legacy.py
@@ -32,9 +32,9 @@
 
 from . import compat_expanduser, compat_HTMLParseError, compat_realpath
 from .compat_utils import passthrough_module
-from ..dependencies import Cryptodome_AES as compat_pycrypto_AES  # noqa: F401
 from ..dependencies import brotli as compat_brotli  # noqa: F401
 from ..dependencies import websockets as compat_websockets  # noqa: F401
+from ..dependencies.Cryptodome import AES as compat_pycrypto_AES  # noqa: F401
 
 passthrough_module(__name__, '...utils', ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'))
 
diff --git a/yt_dlp/compat/compat_utils.py b/yt_dlp/compat/compat_utils.py
index 8956b3bf1..3ca46d270 100644
--- a/yt_dlp/compat/compat_utils.py
+++ b/yt_dlp/compat/compat_utils.py
@@ -48,7 +48,7 @@ def passthrough_module(parent, child, allowed_attributes=(..., ), *, callback=la
     """Passthrough parent module into a child module, creating the parent if necessary"""
     def __getattr__(attr):
         if _is_package(parent):
-            with contextlib.suppress(ImportError):
+            with contextlib.suppress(ModuleNotFoundError):
                 return importlib.import_module(f'.{attr}', parent.__name__)
 
         ret = from_child(attr)
diff --git a/yt_dlp/dependencies/Cryptodome.py b/yt_dlp/dependencies/Cryptodome.py
index 2adc51374..74ab6575c 100644
--- a/yt_dlp/dependencies/Cryptodome.py
+++ b/yt_dlp/dependencies/Cryptodome.py
@@ -1,8 +1,5 @@
 import types
 
-from ..compat import functools
-from ..compat.compat_utils import passthrough_module
-
 try:
     import Cryptodome as _parent
 except ImportError:
@@ -12,19 +9,28 @@
         _parent = types.ModuleType('no_Cryptodome')
         __bool__ = lambda: False
 
-passthrough_module(__name__, _parent, (..., '__version__'))
-del passthrough_module
+__version__ = ''
+AES = PKCS1_v1_5 = Blowfish = PKCS1_OAEP = SHA1 = CMAC = RSA = None
+try:
+    if _parent.__name__ == 'Cryptodome':
+        from Cryptodome import __version__
+        from Cryptodome.Cipher import AES, PKCS1_OAEP, Blowfish, PKCS1_v1_5
+        from Cryptodome.Hash import CMAC, SHA1
+        from Cryptodome.PublicKey import RSA
+    elif _parent.__name__ == 'Crypto':
+        from Crypto import __version__
+        from Crypto.Cipher import AES, PKCS1_OAEP, Blowfish, PKCS1_v1_5  # noqa: F401
+        from Crypto.Hash import CMAC, SHA1  # noqa: F401
+        from Crypto.PublicKey import RSA  # noqa: F401
+except ImportError:
+    __version__ = f'broken {__version__}'.strip()
 
 
-@property
-@functools.cache
-def _yt_dlp__identifier():
-    if _parent.__name__ == 'Crypto':
-        from Crypto.Cipher import AES
-        try:
-            # In pycrypto, mode defaults to ECB. See:
-            # https://www.pycryptodome.org/en/latest/src/vs_pycrypto.html#:~:text=not%20have%20ECB%20as%20default%20mode
-            AES.new(b'abcdefghijklmnop')
-        except TypeError:
-            return 'pycrypto'
-    return _parent.__name__
+_yt_dlp__identifier = _parent.__name__
+if AES and _yt_dlp__identifier == 'Crypto':
+    try:
+        # In pycrypto, mode defaults to ECB. See:
+        # https://www.pycryptodome.org/en/latest/src/vs_pycrypto.html#:~:text=not%20have%20ECB%20as%20default%20mode
+        AES.new(b'abcdefghijklmnop')
+    except TypeError:
+        _yt_dlp__identifier = 'pycrypto'
diff --git a/yt_dlp/dependencies/__init__.py b/yt_dlp/dependencies/__init__.py
index c2214e6db..6e7d29c5c 100644
--- a/yt_dlp/dependencies/__init__.py
+++ b/yt_dlp/dependencies/__init__.py
@@ -73,7 +73,7 @@
 
 
 # Deprecated
-Cryptodome_AES = Cryptodome.Cipher.AES if Cryptodome else None
+Cryptodome_AES = Cryptodome.AES
 
 
 __all__ = [
diff --git a/yt_dlp/downloader/fragment.py b/yt_dlp/downloader/fragment.py
index 039cb1492..3dc638f52 100644
--- a/yt_dlp/downloader/fragment.py
+++ b/yt_dlp/downloader/fragment.py
@@ -466,7 +466,8 @@ def error_callback(err, count, retries):
             for retry in RetryManager(self.params.get('fragment_retries'), error_callback):
                 try:
                     ctx['fragment_count'] = fragment.get('fragment_count')
-                    if not self._download_fragment(ctx, fragment['url'], info_dict, headers):
+                    if not self._download_fragment(
+                            ctx, fragment['url'], info_dict, headers, info_dict.get('request_data')):
                         return
                 except (urllib.error.HTTPError, http.client.IncompleteRead) as err:
                     retry.error = err
@@ -496,7 +497,7 @@ def _download_fragment(fragment):
                 download_fragment(fragment, ctx_copy)
                 return fragment, fragment['frag_index'], ctx_copy.get('fragment_filename_sanitized')
 
-            self.report_warning('The download speed shown is only of one thread. This is a known issue and patches are welcome')
+            self.report_warning('The download speed shown is only of one thread. This is a known issue')
             with tpe or concurrent.futures.ThreadPoolExecutor(max_workers) as pool:
                 try:
                     for fragment, frag_index, frag_filename in pool.map(_download_fragment, fragments):
diff --git a/yt_dlp/downloader/hls.py b/yt_dlp/downloader/hls.py
index 29d6f6241..f2868dc52 100644
--- a/yt_dlp/downloader/hls.py
+++ b/yt_dlp/downloader/hls.py
@@ -70,7 +70,7 @@ def real_download(self, filename, info_dict):
         can_download, message = self.can_download(s, info_dict, self.params.get('allow_unplayable_formats')), None
         if can_download:
             has_ffmpeg = FFmpegFD.available()
-            no_crypto = not Cryptodome and '#EXT-X-KEY:METHOD=AES-128' in s
+            no_crypto = not Cryptodome.AES and '#EXT-X-KEY:METHOD=AES-128' in s
             if no_crypto and has_ffmpeg:
                 can_download, message = False, 'The stream has AES-128 encryption and pycryptodomex is not available'
             elif no_crypto:
diff --git a/yt_dlp/extractor/bilibili.py b/yt_dlp/extractor/bilibili.py
index f4180633a..2252840b3 100644
--- a/yt_dlp/extractor/bilibili.py
+++ b/yt_dlp/extractor/bilibili.py
@@ -894,15 +894,15 @@ def _parse_video_metadata(self, video_data):
         }
 
     def _perform_login(self, username, password):
-        if not Cryptodome:
+        if not Cryptodome.RSA:
             raise ExtractorError('pycryptodomex not found. Please install', expected=True)
 
         key_data = self._download_json(
             'https://passport.bilibili.tv/x/intl/passport-login/web/key?lang=en-US', None,
             note='Downloading login key', errnote='Unable to download login key')['data']
 
-        public_key = Cryptodome.PublicKey.RSA.importKey(key_data['key'])
-        password_hash = Cryptodome.Cipher.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode('utf-8'))
+        public_key = Cryptodome.RSA.importKey(key_data['key'])
+        password_hash = Cryptodome.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode('utf-8'))
         login_post = self._download_json(
             'https://passport.bilibili.tv/x/intl/passport-login/web/login/password?lang=en-US', None, data=urlencode_postdata({
                 'username': username,
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index ebacc87bc..8ad63b411 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -132,6 +132,7 @@ class InfoExtractor:
                                        is parsed from a string (in case of
                                        fragmented media)
                                    for MSS - URL of the ISM manifest.
+                    * request_data  Data to send in POST request to the URL
                     * manifest_url
                                  The URL of the manifest file in case of
                                  fragmented media:
@@ -2063,6 +2064,7 @@ def extract_media(x_media_line):
                     'protocol': entry_protocol,
                     'preference': preference,
                     'quality': quality,
+                    'has_drm': has_drm,
                     'vcodec': 'none' if media_type == 'AUDIO' else None,
                 } for idx in _extract_m3u8_playlist_indices(manifest_url))
 
@@ -2122,6 +2124,7 @@ def build_stream_name():
                         'protocol': entry_protocol,
                         'preference': preference,
                         'quality': quality,
+                        'has_drm': has_drm,
                     }
                     resolution = last_stream_inf.get('RESOLUTION')
                     if resolution:
@@ -3524,7 +3527,7 @@ def description(cls, *, markdown=True, search_examples=None):
         desc = ''
         if cls._NETRC_MACHINE:
             if markdown:
-                desc += f' [<abbr title="netrc machine"><em>{cls._NETRC_MACHINE}</em></abbr>]'
+                desc += f' [*{cls._NETRC_MACHINE}*](## "netrc machine")'
             else:
                 desc += f' [{cls._NETRC_MACHINE}]'
         if cls.IE_DESC is False:
diff --git a/yt_dlp/extractor/generic.py b/yt_dlp/extractor/generic.py
index 55e55d524..d76ef3e31 100644
--- a/yt_dlp/extractor/generic.py
+++ b/yt_dlp/extractor/generic.py
@@ -15,6 +15,7 @@
     UnsupportedError,
     determine_ext,
     dict_get,
+    extract_basic_auth,
     format_field,
     int_or_none,
     is_html,
@@ -2372,9 +2373,8 @@ def _real_extract(self, url):
             **smuggled_data.get('http_headers', {})
         })
         new_url = full_response.geturl()
-        if new_url == urllib.parse.urlparse(url)._replace(scheme='https').geturl():
-            url = new_url
-        elif url != new_url:
+        url = urllib.parse.urlparse(url)._replace(scheme=urllib.parse.urlparse(new_url).scheme).geturl()
+        if new_url != extract_basic_auth(url)[0]:
             self.report_following_redirect(new_url)
             if force_videoid:
                 new_url = smuggle_url(new_url, {'force_videoid': force_videoid})
diff --git a/yt_dlp/extractor/googledrive.py b/yt_dlp/extractor/googledrive.py
index e027ea7c4..9e2ccde00 100644
--- a/yt_dlp/extractor/googledrive.py
+++ b/yt_dlp/extractor/googledrive.py
@@ -3,8 +3,8 @@
 from .common import InfoExtractor
 from ..compat import compat_parse_qs
 from ..utils import (
-    determine_ext,
     ExtractorError,
+    determine_ext,
     get_element_by_class,
     int_or_none,
     lowercase_escape,
@@ -163,15 +163,13 @@ def _real_extract(self, url):
         video_id = self._match_id(url)
         video_info = compat_parse_qs(self._download_webpage(
             'https://drive.google.com/get_video_info',
-            video_id, query={'docid': video_id}))
+            video_id, 'Downloading video webpage', query={'docid': video_id}))
 
         def get_value(key):
             return try_get(video_info, lambda x: x[key][0])
 
         reason = get_value('reason')
         title = get_value('title')
-        if not title and reason:
-            raise ExtractorError(reason, expected=True)
 
         formats = []
         fmt_stream_map = (get_value('fmt_stream_map') or '').split(',')
@@ -216,6 +214,11 @@ def request_source_file(source_url, kind):
         urlh = request_source_file(source_url, 'source')
         if urlh:
             def add_source_format(urlh):
+                nonlocal title
+                if not title:
+                    title = self._search_regex(
+                        r'\bfilename="([^"]+)"', urlh.headers.get('Content-Disposition'),
+                        'title', default=None)
                 formats.append({
                     # Use redirect URLs as download URLs in order to calculate
                     # correct cookies in _calc_cookies.
@@ -251,7 +254,10 @@ def add_source_format(urlh):
                             or 'unable to extract confirmation code')
 
         if not formats and reason:
-            self.raise_no_formats(reason, expected=True)
+            if title:
+                self.raise_no_formats(reason, expected=True)
+            else:
+                raise ExtractorError(reason, expected=True)
 
         hl = get_value('hl')
         subtitles_id = None
diff --git a/yt_dlp/extractor/ivi.py b/yt_dlp/extractor/ivi.py
index 96220bea9..fa5ceec95 100644
--- a/yt_dlp/extractor/ivi.py
+++ b/yt_dlp/extractor/ivi.py
@@ -91,7 +91,7 @@ def _real_extract(self, url):
         for site in (353, 183):
             content_data = (data % site).encode()
             if site == 353:
-                if not Cryptodome:
+                if not Cryptodome.CMAC:
                     continue
 
                 timestamp = (self._download_json(
@@ -105,8 +105,8 @@ def _real_extract(self, url):
 
                 query = {
                     'ts': timestamp,
-                    'sign': Cryptodome.Hash.CMAC.new(self._LIGHT_KEY, timestamp.encode() + content_data,
-                                                     Cryptodome.Cipher.Blowfish).hexdigest(),
+                    'sign': Cryptodome.CMAC.new(self._LIGHT_KEY, timestamp.encode() + content_data,
+                                                Cryptodome.Blowfish).hexdigest(),
                 }
             else:
                 query = {}
@@ -126,7 +126,7 @@ def _real_extract(self, url):
                     extractor_msg = 'Video %s does not exist'
                 elif site == 353:
                     continue
-                elif not Cryptodome:
+                elif not Cryptodome.CMAC:
                     raise ExtractorError('pycryptodomex not found. Please install', expected=True)
                 elif message:
                     extractor_msg += ': ' + message
diff --git a/yt_dlp/extractor/wrestleuniverse.py b/yt_dlp/extractor/wrestleuniverse.py
index 78e7c83ab..5c6dec2c4 100644
--- a/yt_dlp/extractor/wrestleuniverse.py
+++ b/yt_dlp/extractor/wrestleuniverse.py
@@ -50,10 +50,10 @@ def _call_api(self, video_id, param='', msg='API', auth=True, data=None, query={
             data=data, headers=headers, query=query, fatal=fatal)
 
     def _call_encrypted_api(self, video_id, param='', msg='API', data={}, query={}, fatal=True):
-        if not Cryptodome:
+        if not Cryptodome.RSA:
             raise ExtractorError('pycryptodomex not found. Please install', expected=True)
-        private_key = Cryptodome.PublicKey.RSA.generate(2048)
-        cipher = Cryptodome.Cipher.PKCS1_OAEP.new(private_key, hashAlgo=Cryptodome.Hash.SHA1)
+        private_key = Cryptodome.RSA.generate(2048)
+        cipher = Cryptodome.PKCS1_OAEP.new(private_key, hashAlgo=Cryptodome.SHA1)
 
         def decrypt(data):
             if not data:
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index be82bc689..b02e0153a 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -956,7 +956,7 @@ def _extract_response(self, item_id, query, note='Downloading API JSON', headers
 
     @staticmethod
     def is_music_url(url):
-        return re.match(r'https?://music\.youtube\.com/', url) is not None
+        return re.match(r'(https?://)?music\.youtube\.com/', url) is not None
 
     def _extract_video(self, renderer):
         video_id = renderer.get('videoId')
@@ -3341,6 +3341,13 @@ def extract_thread(contents):
                 comment = self._extract_comment(comment_renderer, parent)
                 if not comment:
                     continue
+                # Sometimes YouTube may break and give us infinite looping comments.
+                # See: https://github.com/yt-dlp/yt-dlp/issues/6290
+                if comment['id'] in tracker['seen_comment_ids']:
+                    self.report_warning('Detected YouTube comments looping. Stopping comment extraction as we probably cannot get any more.')
+                    yield
+                else:
+                    tracker['seen_comment_ids'].add(comment['id'])
 
                 tracker['running_total'] += 1
                 tracker['total_reply_comments' if parent else 'total_parent_comments'] += 1
@@ -3365,7 +3372,8 @@ def extract_thread(contents):
                 est_total=0,
                 current_page_thread=0,
                 total_parent_comments=0,
-                total_reply_comments=0)
+                total_reply_comments=0,
+                seen_comment_ids=set())
 
         # TODO: Deprecated
         # YouTube comments have a max depth of 2
@@ -3776,10 +3784,18 @@ def _extract_formats_and_subtitles(self, streaming_data, video_id, player_url, l
             if no_video:
                 dct['abr'] = tbr
             if no_audio or no_video:
-                dct['downloader_options'] = {
-                    # Youtube throttles chunks >~10M
-                    'http_chunk_size': 10485760,
-                }
+                CHUNK_SIZE = 10 << 20
+                dct.update({
+                    'protocol': 'http_dash_segments',
+                    'fragments': [{
+                        'url': update_url_query(dct['url'], {
+                            'range': f'{range_start}-{min(range_start + CHUNK_SIZE - 1, dct["filesize"])}'
+                        })
+                    } for range_start in range(0, dct['filesize'], CHUNK_SIZE)]
+                } if dct['filesize'] else {
+                    'downloader_options': {'http_chunk_size': CHUNK_SIZE}  # No longer useful?
+                })
+
                 if dct.get('ext'):
                     dct['container'] = dct['ext'] + '_dash'
 
@@ -4897,6 +4913,10 @@ def _get_uncropped(url):
         info['view_count'] = self._get_count(playlist_stats, 1)
         if info['view_count'] is None:  # 0 is allowed
             info['view_count'] = self._get_count(playlist_header_renderer, 'viewCountText')
+        if info['view_count'] is None:
+            info['view_count'] = self._get_count(data, (
+                'contents', 'twoColumnBrowseResultsRenderer', 'tabs', ..., 'tabRenderer', 'content', 'sectionListRenderer',
+                'contents', ..., 'itemSectionRenderer', 'contents', ..., 'channelAboutFullMetadataRenderer', 'viewCountText'))
 
         info['playlist_count'] = self._get_count(playlist_stats, 0)
         if info['playlist_count'] is None:  # 0 is allowed
@@ -6116,6 +6136,23 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             }
         }],
         'params': {'extract_flat': True},
+    }, {
+        'url': 'https://www.youtube.com/@3blue1brown/about',
+        'info_dict': {
+            'id': 'UCYO_jab_esuFRV4b17AJtAw',
+            'tags': ['Mathematics'],
+            'title': '3Blue1Brown - About',
+            'uploader_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
+            'channel_follower_count': int,
+            'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',
+            'uploader_id': 'UCYO_jab_esuFRV4b17AJtAw',
+            'channel': '3Blue1Brown',
+            'uploader': '3Blue1Brown',
+            'view_count': int,
+            'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
+            'description': 'md5:e1384e8a133307dd10edee76e875d62f',
+        },
+        'playlist_count': 0,
     }]
 
     @classmethod
@@ -6182,6 +6219,8 @@ def _real_extract(self, url, smuggled_data):
         original_tab_id, display_id = tab[1:], f'{item_id}{tab}'
         if is_channel and not tab and 'no-youtube-channel-redirect' not in compat_opts:
             url = f'{pre}/videos{post}'
+        if smuggled_data.get('is_music_url'):
+            self.report_warning(f'YouTube Music is not directly supported. Redirecting to {url}')
 
         # Handle both video/playlist URLs
         qs = parse_qs(url)
diff --git a/yt_dlp/plugins.py b/yt_dlp/plugins.py
index 6eecdb4d0..6422c7a51 100644
--- a/yt_dlp/plugins.py
+++ b/yt_dlp/plugins.py
@@ -88,7 +88,7 @@ def _get_package_paths(*root_paths, containing_folder='plugins'):
             candidate = path / parts
             if candidate.is_dir():
                 yield candidate
-            elif path.suffix in ('.zip', '.egg', '.whl'):
+            elif path.suffix in ('.zip', '.egg', '.whl') and path.is_file():
                 if parts in dirs_in_zip(path):
                     yield candidate
 
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
index 994239897..9ff096433 100644
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -593,21 +593,43 @@ def clean_html(html):
 
 
 class LenientJSONDecoder(json.JSONDecoder):
-    def __init__(self, *args, transform_source=None, ignore_extra=False, **kwargs):
+    # TODO: Write tests
+    def __init__(self, *args, transform_source=None, ignore_extra=False, close_objects=0, **kwargs):
         self.transform_source, self.ignore_extra = transform_source, ignore_extra
+        self._close_attempts = 2 * close_objects
         super().__init__(*args, **kwargs)
 
+    @staticmethod
+    def _close_object(err):
+        doc = err.doc[:err.pos]
+        # We need to add comma first to get the correct error message
+        if err.msg.startswith('Expecting \',\''):
+            return doc + ','
+        elif not doc.endswith(','):
+            return
+
+        if err.msg.startswith('Expecting property name'):
+            return doc[:-1] + '}'
+        elif err.msg.startswith('Expecting value'):
+            return doc[:-1] + ']'
+
     def decode(self, s):
         if self.transform_source:
             s = self.transform_source(s)
-        try:
-            if self.ignore_extra:
-                return self.raw_decode(s.lstrip())[0]
-            return super().decode(s)
-        except json.JSONDecodeError as e:
-            if e.pos is not None:
+        for attempt in range(self._close_attempts + 1):
+            try:
+                if self.ignore_extra:
+                    return self.raw_decode(s.lstrip())[0]
+                return super().decode(s)
+            except json.JSONDecodeError as e:
+                if e.pos is None:
+                    raise
+                elif attempt < self._close_attempts:
+                    s = self._close_object(e)
+                    if s is not None:
+                        continue
                 raise type(e)(f'{e.msg} in {s[e.pos-10:e.pos+10]!r}', s, e.pos)
-            raise
+        assert False, 'Too many attempts to decode JSON'
 
 
 def sanitize_open(filename, open_mode):
@@ -879,6 +901,7 @@ def __init__(self, *args, env=None, text=False, **kwargs):
             env = os.environ.copy()
         self._fix_pyinstaller_ld_path(env)
 
+        self.__text_mode = kwargs.get('encoding') or kwargs.get('errors') or text or kwargs.get('universal_newlines')
         if text is True:
             kwargs['universal_newlines'] = True  # For 3.6 compatibility
             kwargs.setdefault('encoding', 'utf-8')
@@ -900,7 +923,7 @@ def kill(self, *, timeout=0):
     @classmethod
     def run(cls, *args, timeout=None, **kwargs):
         with cls(*args, **kwargs) as proc:
-            default = '' if proc.text_mode else b''
+            default = '' if proc.__text_mode else b''
             stdout, stderr = proc.communicate_or_kill(timeout=timeout)
             return stdout or default, stderr or default, proc.returncode
 


Test patch:
diff --git a/test/test_aes.py b/test/test_aes.py
index 18f15fecb..a26abfd7d 100644
--- a/test/test_aes.py
+++ b/test/test_aes.py
@@ -48,7 +48,7 @@ def test_cbc_decrypt(self):
         data = b'\x97\x92+\xe5\x0b\xc3\x18\x91ky9m&\xb3\xb5@\xe6\x27\xc2\x96.\xc8u\x88\xab9-[\x9e|\xf1\xcd'
         decrypted = intlist_to_bytes(aes_cbc_decrypt(bytes_to_intlist(data), self.key, self.iv))
         self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
-        if Cryptodome:
+        if Cryptodome.AES:
             decrypted = aes_cbc_decrypt_bytes(data, intlist_to_bytes(self.key), intlist_to_bytes(self.iv))
             self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
 
@@ -78,7 +78,7 @@ def test_gcm_decrypt(self):
         decrypted = intlist_to_bytes(aes_gcm_decrypt_and_verify(
             bytes_to_intlist(data), self.key, bytes_to_intlist(authentication_tag), self.iv[:12]))
         self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
-        if Cryptodome:
+        if Cryptodome.AES:
             decrypted = aes_gcm_decrypt_and_verify_bytes(
                 data, intlist_to_bytes(self.key), authentication_tag, intlist_to_bytes(self.iv[:12]))
             self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)