Repository: yt-dlp/yt-dlp
Pull request #12299 (2025-02-07 10:33:51)

[test:download] Validate and sort info dict fields
**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

-->

These `missing_keys` in the hint have not been sorted since it was added (a9c2896e2252839c2e4801189f10acce7ff6413e).

```
[info] Writing video metadata as JSON to: ....json

        'info_dict': {
            'id': '...',
            'title': '...',

            'upload_date': '...',
            'age_limit': ...,
            'timestamp': ...,
        },
        F
======================================================================
FAIL: test_Webpage_all (__main__.TestDownload):
----------------------------------------------------------------------
Traceback (most recent call last):
  ...
  File ".../yt-dlp/test/helper.py", line 274, in expect_info_dict
    self.assertFalse(
    ~~~~~~~~~~~~~~~~^
        missing_keys,
        ^^^^^^^^^^^^^
        'Missing keys in test definition: {}'.format(', '.join(sorted(missing_keys))))
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: {'upload_date', 'age_limit', 'timestamp'} is not false : Missing keys in test definition: age_limit, timestamp, upload_date

```

<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

### 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*?
- [ ] 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>



Patch:
diff --git a/.github/ISSUE_TEMPLATE/1_broken_site.yml b/.github/ISSUE_TEMPLATE/1_broken_site.yml
index 20e5e944f..fd7591ba2 100644
--- a/.github/ISSUE_TEMPLATE/1_broken_site.yml
+++ b/.github/ISSUE_TEMPLATE/1_broken_site.yml
@@ -2,13 +2,11 @@ name: Broken site support
 description: Report issue with yt-dlp on a supported site
 labels: [triage, site-bug]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: checkboxes
     id: checklist
     attributes:
@@ -24,9 +22,7 @@ body:
           required: true
         - label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar issues **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required
   - type: input
@@ -47,6 +43,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
@@ -78,11 +76,3 @@ body:
       render: shell
     validations:
       required: true
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/2_site_support_request.yml b/.github/ISSUE_TEMPLATE/2_site_support_request.yml
index 4aeff7dc6..111f423d0 100644
--- a/.github/ISSUE_TEMPLATE/2_site_support_request.yml
+++ b/.github/ISSUE_TEMPLATE/2_site_support_request.yml
@@ -2,13 +2,11 @@ name: Site support request
 description: Request support for a new site
 labels: [triage, site-request]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: checkboxes
     id: checklist
     attributes:
@@ -24,9 +22,7 @@ body:
           required: true
         - label: I've checked that none of provided URLs [violate any copyrights](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and am willing to share it if required
   - type: input
@@ -59,6 +55,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
@@ -90,11 +88,3 @@ body:
       render: shell
     validations:
       required: true
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/3_site_feature_request.yml b/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
index 2f516ebb7..fd74be331 100644
--- a/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/3_site_feature_request.yml
@@ -1,14 +1,12 @@
 name: Site feature request
-description: Request a new functionality for a supported site
+description: Request new functionality for a site supported by yt-dlp
 labels: [triage, site-enhancement]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: checkboxes
     id: checklist
     attributes:
@@ -22,9 +20,7 @@ body:
           required: true
         - label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required
   - type: input
@@ -55,6 +51,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
@@ -86,11 +84,3 @@ body:
       render: shell
     validations:
       required: true
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/4_bug_report.yml b/.github/ISSUE_TEMPLATE/4_bug_report.yml
index 201586e9d..e9ec45e14 100644
--- a/.github/ISSUE_TEMPLATE/4_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/4_bug_report.yml
@@ -2,13 +2,11 @@ name: Core bug report
 description: Report a bug unrelated to any particular site or extractor
 labels: [triage, bug]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: checkboxes
     id: checklist
     attributes:
@@ -20,13 +18,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
-          required: true
-        - label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
-          required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar issues **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: description
@@ -40,6 +32,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
@@ -71,11 +65,3 @@ body:
       render: shell
     validations:
       required: true
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/5_feature_request.yml b/.github/ISSUE_TEMPLATE/5_feature_request.yml
index 765de86a2..9c7d66d81 100644
--- a/.github/ISSUE_TEMPLATE/5_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/5_feature_request.yml
@@ -1,14 +1,12 @@
 name: Feature request
-description: Request a new functionality unrelated to any particular site or extractor
+description: Request a new feature unrelated to any particular site or extractor
 labels: [triage, enhancement]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: checkboxes
     id: checklist
     attributes:
@@ -22,9 +20,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: description
@@ -38,6 +34,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       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"
@@ -65,11 +63,3 @@ body:
         [youtube] Extracting URL: https://www.youtube.com/watch?v=BaW_jenozKc
         <more lines>
       render: shell
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/6_question.yml b/.github/ISSUE_TEMPLATE/6_question.yml
index 198e21bec..186f73a4a 100644
--- a/.github/ISSUE_TEMPLATE/6_question.yml
+++ b/.github/ISSUE_TEMPLATE/6_question.yml
@@ -1,14 +1,12 @@
 name: Ask question
-description: Ask yt-dlp related question
+description: Ask a question about using yt-dlp
 labels: [question]
 body:
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
   - type: markdown
     attributes:
       value: |
@@ -28,9 +26,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%3Aissue%20-label%3Aspam%20%20) for similar questions **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: question
@@ -44,6 +40,8 @@ body:
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       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"
@@ -71,11 +69,3 @@ body:
         [youtube] Extracting URL: https://www.youtube.com/watch?v=BaW_jenozKc
         <more lines>
       render: shell
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 9cdffa4b1..0131631bb 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,5 @@
 blank_issues_enabled: false
 contact_links:
-  - name: Get help from the community on Discord
+  - name: Get help on Discord
     url: https://discord.gg/H5MNcFW63r
-    about: Join the yt-dlp Discord for community-powered support!
-  - name: Matrix Bridge to the Discord server
-    url: https://matrix.to/#/#yt-dlp:matrix.org
-    about: For those who do not want to use Discord
+    about: Join the yt-dlp Discord server for support and discussion
diff --git a/.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml b/.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml
index bff28ae4e..f1a2d3090 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml
@@ -18,9 +18,7 @@ body:
           required: true
         - label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar issues **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required
   - type: input
diff --git a/.github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml b/.github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml
index 2bffe738d..31b89b683 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/2_site_support_request.yml
@@ -18,9 +18,7 @@ body:
           required: true
         - label: I've checked that none of provided URLs [violate any copyrights](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and am willing to share it if required
   - type: input
diff --git a/.github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml b/.github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml
index 6c3127983..421766a75 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/3_site_feature_request.yml
@@ -1,5 +1,5 @@
 name: Site feature request
-description: Request a new functionality for a supported site
+description: Request new functionality for a site supported by yt-dlp
 labels: [triage, site-enhancement]
 body:
   %(no_skip)s
@@ -16,9 +16,7 @@ body:
           required: true
         - label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
         - label: I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required
   - type: input
diff --git a/.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml b/.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml
index 5f357d96e..31a19b292 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml
@@ -14,13 +14,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
-          required: true
-        - label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
-          required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar issues **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: description
diff --git a/.github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml b/.github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml
index 99107ff58..b8ab6610b 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/5_feature_request.yml
@@ -1,5 +1,5 @@
 name: Feature request
-description: Request a new functionality unrelated to any particular site or extractor
+description: Request a new feature unrelated to any particular site or extractor
 labels: [triage, enhancement]
 body:
   %(no_skip)s
@@ -16,9 +16,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar requests **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: description
diff --git a/.github/ISSUE_TEMPLATE_tmpl/6_question.yml b/.github/ISSUE_TEMPLATE_tmpl/6_question.yml
index bd742109a..062e96321 100644
--- a/.github/ISSUE_TEMPLATE_tmpl/6_question.yml
+++ b/.github/ISSUE_TEMPLATE_tmpl/6_question.yml
@@ -1,5 +1,5 @@
 name: Ask question
-description: Ask yt-dlp related question
+description: Ask a question about using yt-dlp
 labels: [question]
 body:
   %(no_skip)s
@@ -22,9 +22,7 @@ body:
           required: true
         - label: I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
           required: true
-        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions **including closed ones**. DO NOT post duplicates
-          required: true
-        - label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
+        - label: I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766), [the FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ), and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=is%%3Aissue%%20-label%%3Aspam%%20%%20) for similar questions **including closed ones**. DO NOT post duplicates
           required: true
   - type: textarea
     id: question
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 4deee572f..4dcfcc48c 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,14 +1,17 @@
-**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
-
+    **IMPORTANT**: PRs without the template will be CLOSED
+    
+    Due to the high volume of pull requests, it may be a while before your PR is reviewed.
+    Please try to keep your pull request focused on a single bugfix or new feature.
+    Pull requests with a vast scope and/or very large diff will take much longer to review.
+    It is recommended for new contributors to stick to smaller pull requests, so you can receive much more immediate feedback as you familiarize yourself with the codebase.
+
+    PLEASE AVOID FORCE-PUSHING after opening a PR, as it makes reviewing more difficult.
 -->
 
-ADD DESCRIPTION HERE
+### Description of your *pull request* and other information
+
+ADD DETAILED DESCRIPTION HERE
 
 Fixes #
 
@@ -16,24 +19,22 @@ ### Description of your *pull request* and other information
 <details open><summary>Template</summary> <!-- OPEN is intentional -->
 
 <!--
+    # PLEASE FOLLOW THE GUIDE BELOW
 
-# 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
-
+    - 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 what your *pull request* will actually look like
 -->
 
 ### Before submitting a *pull request* make sure you have:
 - [ ] 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)
 - [ ] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
 
-### 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:
-- [ ] 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)
+### 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 those that apply and remove the others:
+- [ ] I am the original author of the code in this PR, and I am willing to release it under [Unlicense](http://unlicense.org/)
+- [ ] I am not the original author of the code in this PR, but it is in the public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)
 
-### What is the purpose of your *pull request*?
+### What is the purpose of your *pull request*? Check those that apply and remove the others:
 - [ ] 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
diff --git a/README.md b/README.md
index 45c56434a..0ac27c462 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,6 @@
 [![Release version](https://img.shields.io/github/v/release/yt-dlp/yt-dlp?color=brightgreen&label=Download&style=for-the-badge)](#installation "Installation")
 [![PyPI](https://img.shields.io/badge/-PyPI-blue.svg?logo=pypi&labelColor=555555&style=for-the-badge)](https://pypi.org/project/yt-dlp "PyPI")
 [![Donate](https://img.shields.io/badge/_-Donate-red.svg?logo=githubsponsors&labelColor=555555&style=for-the-badge)](Collaborators.md#collaborators "Donate")
-[![Matrix](https://img.shields.io/matrix/yt-dlp:matrix.org?color=brightgreen&labelColor=555555&label=&logo=element&style=for-the-badge)](https://matrix.to/#/#yt-dlp:matrix.org "Matrix")
 [![Discord](https://img.shields.io/discord/807245652072857610?color=blue&labelColor=555555&label=&logo=discord&style=for-the-badge)](https://discord.gg/H5MNcFW63r "Discord")
 [![Supported Sites](https://img.shields.io/badge/-Supported_Sites-brightgreen.svg?style=for-the-badge)](supportedsites.md "Supported Sites")
 [![License: Unlicense](https://img.shields.io/badge/-Unlicense-blue.svg?style=for-the-badge)](LICENSE "License")
diff --git a/devscripts/make_issue_template.py b/devscripts/make_issue_template.py
index 2a418ddbf..681ebc51c 100644
--- a/devscripts/make_issue_template.py
+++ b/devscripts/make_issue_template.py
@@ -11,11 +11,13 @@
 
 from devscripts.utils import get_filename_args, read_file, write_file
 
-VERBOSE_TMPL = '''
+VERBOSE = '''
   - type: checkboxes
     id: verbose
     attributes:
       label: Provide verbose output that clearly demonstrates the problem
+      description: |
+        This is mandatory unless absolutely impossible to provide. If you are unable to provide the output, please explain why.
       options:
         - label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
           required: true
@@ -47,31 +49,23 @@
       render: shell
     validations:
       required: true
-  - type: markdown
-    attributes:
-      value: |
-        > [!CAUTION]
-        > ### GitHub is experiencing a high volume of malicious spam comments.
-        > ### If you receive any replies asking you download a file, do NOT follow the download links!
-        >
-        > Note that this issue may be temporarily locked as an anti-spam measure after it is opened.
 '''.strip()
 
 NO_SKIP = '''
-  - type: checkboxes
+  - type: markdown
     attributes:
-      label: DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE
-      description: Fill all fields even if you think it is irrelevant for the issue
-      options:
-        - label: I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\\* field
-          required: true
+      value: |
+        > [!IMPORTANT]
+        > Not providing the required (*) information will result in your issue being closed and ignored.
 '''.strip()
 
 
 def main():
-    fields = {'no_skip': NO_SKIP}
-    fields['verbose'] = VERBOSE_TMPL % fields
-    fields['verbose_optional'] = re.sub(r'(\n\s+validations:)?\n\s+required: true', '', fields['verbose'])
+    fields = {
+        'no_skip': NO_SKIP,
+        'verbose': VERBOSE,
+        'verbose_optional': re.sub(r'(\n\s+validations:)?\n\s+required: true', '', VERBOSE),
+    }
 
     infile, outfile = get_filename_args(has_infile=True)
     write_file(outfile, read_file(infile) % fields)
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index b7b19cf6e..3293a9076 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -598,7 +598,7 @@ class YoutubeDL:
         # NB: Keep in sync with the docstring of extractor/common.py
         'url', 'manifest_url', 'manifest_stream_number', 'ext', 'format', 'format_id', 'format_note',
         'width', 'height', 'aspect_ratio', 'resolution', 'dynamic_range', 'tbr', 'abr', 'acodec', 'asr', 'audio_channels',
-        'vbr', 'fps', 'vcodec', 'container', 'filesize', 'filesize_approx', 'rows', 'columns',
+        'vbr', 'fps', 'vcodec', 'container', 'filesize', 'filesize_approx', 'rows', 'columns', 'hls_media_playlist_data',
         'player_url', 'protocol', 'fragment_base_url', 'fragments', 'is_from_start', 'is_dash_periods', 'request_data',
         'preference', 'language', 'language_preference', 'quality', 'source_preference', 'cookies',
         'http_headers', 'stretched_ratio', 'no_resume', 'has_drm', 'extra_param_to_segment_url', 'extra_param_to_key_url',
diff --git a/yt_dlp/downloader/hls.py b/yt_dlp/downloader/hls.py
index da2574da7..7a47f8f83 100644
--- a/yt_dlp/downloader/hls.py
+++ b/yt_dlp/downloader/hls.py
@@ -72,11 +72,15 @@ def check_results():
 
     def real_download(self, filename, info_dict):
         man_url = info_dict['url']
-        self.to_screen(f'[{self.FD_NAME}] Downloading m3u8 manifest')
 
-        urlh = self.ydl.urlopen(self._prepare_url(info_dict, man_url))
-        man_url = urlh.url
-        s = urlh.read().decode('utf-8', 'ignore')
+        s = info_dict.get('hls_media_playlist_data')
+        if s:
+            self.to_screen(f'[{self.FD_NAME}] Using m3u8 manifest from extracted info')
+        else:
+            self.to_screen(f'[{self.FD_NAME}] Downloading m3u8 manifest')
+            urlh = self.ydl.urlopen(self._prepare_url(info_dict, man_url))
+            man_url = urlh.url
+            s = urlh.read().decode('utf-8', 'ignore')
 
         can_download, message = self.can_download(s, info_dict, self.params.get('allow_unplayable_formats')), None
         if can_download:
@@ -177,6 +181,7 @@ def is_ad_fragment_end(s):
         if external_aes_iv:
             external_aes_iv = binascii.unhexlify(remove_start(external_aes_iv, '0x').zfill(32))
         byte_range = {}
+        byte_range_offset = 0
         discontinuity_count = 0
         frag_index = 0
         ad_frag_next = False
@@ -204,6 +209,11 @@ def is_ad_fragment_end(s):
                     })
                     media_sequence += 1
 
+                    # If the byte_range is truthy, reset it after appending a fragment that uses it
+                    if byte_range:
+                        byte_range_offset = byte_range['end']
+                        byte_range = {}
+
                 elif line.startswith('#EXT-X-MAP'):
                     if format_index and discontinuity_count != format_index:
                         continue
@@ -217,10 +227,12 @@ def is_ad_fragment_end(s):
                     if extra_segment_query:
                         frag_url = update_url_query(frag_url, extra_segment_query)
 
+                    map_byte_range = {}
+
                     if map_info.get('BYTERANGE'):
                         splitted_byte_range = map_info.get('BYTERANGE').split('@')
-                        sub_range_start = int(splitted_byte_range[1]) if len(splitted_byte_range) == 2 else byte_range['end']
-                        byte_range = {
+                        sub_range_start = int(splitted_byte_range[1]) if len(splitted_byte_range) == 2 else 0
+                        map_byte_range = {
                             'start': sub_range_start,
                             'end': sub_range_start + int(splitted_byte_range[0]),
                         }
@@ -229,7 +241,7 @@ def is_ad_fragment_end(s):
                         'frag_index': frag_index,
                         'url': frag_url,
                         'decrypt_info': decrypt_info,
-                        'byte_range': byte_range,
+                        'byte_range': map_byte_range,
                         'media_sequence': media_sequence,
                     })
                     media_sequence += 1
@@ -257,7 +269,7 @@ def is_ad_fragment_end(s):
                     media_sequence = int(line[22:])
                 elif line.startswith('#EXT-X-BYTERANGE'):
                     splitted_byte_range = line[17:].split('@')
-                    sub_range_start = int(splitted_byte_range[1]) if len(splitted_byte_range) == 2 else byte_range['end']
+                    sub_range_start = int(splitted_byte_range[1]) if len(splitted_byte_range) == 2 else byte_range_offset
                     byte_range = {
                         'start': sub_range_start,
                         'end': sub_range_start + int(splitted_byte_range[0]),
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index 92ddad2b7..3e7734ce1 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -201,6 +201,11 @@ class InfoExtractor:
                                             fragment_base_url
                                  * "duration" (optional, int or float)
                                  * "filesize" (optional, int)
+                    * hls_media_playlist_data
+                                 The M3U8 media playlist data as a string.
+                                 Only use if the data must be modified during extraction and
+                                 the native HLS downloader should bypass requesting the URL.
+                                 Does not apply if ffmpeg is used as external downloader
                     * is_from_start  Is a live format that can be downloaded
                                 from the start. Boolean
                     * preference Order number of this format. If this field is
diff --git a/yt_dlp/extractor/francetv.py b/yt_dlp/extractor/francetv.py
index 01b18d8da..c6036b306 100644
--- a/yt_dlp/extractor/francetv.py
+++ b/yt_dlp/extractor/francetv.py
@@ -358,7 +358,8 @@ def _real_extract(self, url):
             # For livestreams we need the id of the stream instead of the currently airing episode id
             video_id = traverse_obj(nextjs_data, (
                 ..., ..., 'children', ..., 'children', ..., 'children', ..., 'children', ..., ...,
-                'children', ..., ..., 'children', ..., ..., 'children', ..., 'options', 'id', {str}, any))
+                'children', ..., ..., 'children', ..., ..., 'children', (..., (..., ...)),
+                'options', 'id', {str}, any))
         else:
             video_id = traverse_obj(nextjs_data, (
                 ..., ..., ..., 'children',
diff --git a/yt_dlp/extractor/generic.py b/yt_dlp/extractor/generic.py
index 320a47772..67c224e50 100644
--- a/yt_dlp/extractor/generic.py
+++ b/yt_dlp/extractor/generic.py
@@ -293,6 +293,19 @@ class GenericIE(InfoExtractor):
                 'timestamp': 1378272859.0,
             },
         },
+        # Live DASH MPD
+        {
+            'url': 'https://livesim2.dashif.org/livesim2/ato_10/testpic_2s/Manifest.mpd',
+            'info_dict': {
+                'id': 'Manifest',
+                'ext': 'mp4',
+                'title': r're:Manifest \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
+                'live_status': 'is_live',
+            },
+            'params': {
+                'skip_download': 'livestream',
+            },
+        },
         # m3u8 served with Content-Type: audio/x-mpegURL; charset=utf-8
         {
             'url': 'http://once.unicornmedia.com/now/master/playlist/bb0b18ba-64f5-4b1b-a29f-0ac252f06b68/77a785f3-5188-4806-b788-0893a61634ed/93677179-2d99-4ef4-9e17-fe70d49abfbf/content.m3u8',
@@ -2436,10 +2449,9 @@ def _real_extract(self, url):
             subtitles = {}
             if format_id.endswith('mpegurl') or ext == 'm3u8':
                 formats, subtitles = self._extract_m3u8_formats_and_subtitles(url, video_id, 'mp4', headers=headers)
-            elif format_id.endswith(('mpd', 'dash+xml')) or ext == 'mpd':
-                formats, subtitles = self._extract_mpd_formats_and_subtitles(url, video_id, headers=headers)
             elif format_id == 'f4m' or ext == 'f4m':
                 formats = self._extract_f4m_formats(url, video_id, headers=headers)
+            # Don't check for DASH/mpd here, do it later w/ first_bytes. Same number of requests either way
             else:
                 formats = [{
                     'format_id': format_id,
@@ -2521,6 +2533,7 @@ def _real_extract(self, url):
                     doc,
                     mpd_base_url=full_response.url.rpartition('/')[0],
                     mpd_url=url)
+                info_dict['live_status'] = 'is_live' if doc.get('type') == 'dynamic' else None
                 self._extra_manifest_info(info_dict, url)
                 self.report_detected('DASH manifest')
                 return info_dict
diff --git a/yt_dlp/extractor/twitter.py b/yt_dlp/extractor/twitter.py
index c05b5bf9c..d32ae3f18 100644
--- a/yt_dlp/extractor/twitter.py
+++ b/yt_dlp/extractor/twitter.py
@@ -1,11 +1,12 @@
 import functools
 import json
-import random
+import math
 import re
 import urllib.parse
 
 from .common import InfoExtractor
 from .periscope import PeriscopeBaseIE, PeriscopeIE
+from ..jsinterp import js_number_to_string
 from ..networking.exceptions import HTTPError
 from ..utils import (
     ExtractorError,
@@ -1330,6 +1331,11 @@ def _build_graphql_query(self, media_id):
             },
         }
 
+    def _generate_syndication_token(self, twid):
+        # ((Number(twid) / 1e15) * Math.PI).toString(36).replace(/(0+|\.)/g, '')
+        translation = str.maketrans(dict.fromkeys('0.'))
+        return js_number_to_string((int(twid) / 1e15) * math.PI, 36).translate(translation)
+
     def _call_syndication_api(self, twid):
         self.report_warning(
             'Not all metadata or media is available via syndication endpoint', twid, only_once=True)
@@ -1337,8 +1343,7 @@ def _call_syndication_api(self, twid):
             'https://cdn.syndication.twimg.com/tweet-result', twid, 'Downloading syndication JSON',
             headers={'User-Agent': 'Googlebot'}, query={
                 'id': twid,
-                # TODO: token = ((Number(twid) / 1e15) * Math.PI).toString(36).replace(/(0+|\.)/g, '')
-                'token': ''.join(random.choices('123456789abcdefghijklmnopqrstuvwxyz', k=10)),
+                'token': self._generate_syndication_token(twid),
             })
         if not status:
             raise ExtractorError('Syndication endpoint returned empty JSON response')
diff --git a/yt_dlp/extractor/zdf.py b/yt_dlp/extractor/zdf.py
index 703766cd7..b64a88f6c 100644
--- a/yt_dlp/extractor/zdf.py
+++ b/yt_dlp/extractor/zdf.py
@@ -187,12 +187,20 @@ class ZDFIE(ZDFBaseIE):
         'info_dict': {
             'id': '151025_magie_farben2_tex',
             'ext': 'mp4',
+            'duration': 2615.0,
             'title': 'Die Magie der Farben (2/2)',
             'description': 'md5:a89da10c928c6235401066b60a6d5c1a',
-            'duration': 2615,
             'timestamp': 1465021200,
-            'upload_date': '20160604',
             'thumbnail': 'https://www.zdf.de/assets/mauve-im-labor-100~768x432?cb=1464909117806',
+            'upload_date': '20160604',
+            'episode': 'Die Magie der Farben (2/2)',
+            'episode_id': 'POS_954f4170-36a5-4a41-a6cf-78f1f3b1f127',
+            'season': 'Staffel 1',
+            'series': 'Die Magie der Farben',
+            'season_number': 1,
+            'series_id': 'a39900dd-cdbd-4a6a-a413-44e8c6ae18bc',
+            'season_id': '5a92e619-8a0f-4410-a3d5-19c76fbebb37',
+            'episode_number': 2,
         },
     }, {
         'url': 'https://www.zdf.de/funk/druck-11790/funk-alles-ist-verzaubert-102.html',
@@ -200,12 +208,13 @@ class ZDFIE(ZDFBaseIE):
         'info_dict': {
             'ext': 'mp4',
             'id': 'video_funk_1770473',
-            'duration': 1278,
-            'description': 'Die Neue an der Schule verdreht Ismail den Kopf.',
+            'duration': 1278.0,
             'title': 'Alles ist verzaubert',
+            'description': 'Die Neue an der Schule verdreht Ismail den Kopf.',
             'timestamp': 1635520560,
-            'upload_date': '20211029',
             'thumbnail': 'https://www.zdf.de/assets/teaser-funk-alles-ist-verzaubert-102~1920x1080?cb=1663848412907',
+            'upload_date': '20211029',
+            'episode': 'Alles ist verzaubert',
         },
     }, {
         # Same as https://www.phoenix.de/sendungen/dokumentationen/gesten-der-maechtigen-i-a-89468.html?ref=suche
@@ -248,22 +257,52 @@ class ZDFIE(ZDFBaseIE):
             'title': 'Das Geld anderer Leute',
             'description': 'md5:cb6f660850dc5eb7d1ab776ea094959d',
             'duration': 2581.0,
-            'timestamp': 1675160100,
-            'upload_date': '20230131',
+            'timestamp': 1728983700,
+            'upload_date': '20241015',
             'thumbnail': 'https://epg-image.zdf.de/fotobase-webdelivery/images/e2d7e55a-09f0-424e-ac73-6cac4dd65f35?layout=2400x1350',
+            'series': 'SOKO Stuttgart',
+            'series_id': 'f862ce9a-6dd1-4388-a698-22b36ac4c9e9',
+            'season': 'Staffel 11',
+            'season_number': 11,
+            'season_id': 'ae1b4990-6d87-4970-a571-caccf1ba2879',
+            'episode': 'Das Geld anderer Leute',
+            'episode_number': 10,
+            'episode_id': 'POS_7f367934-f2f0-45cb-9081-736781ff2d23',
         },
     }, {
         'url': 'https://www.zdf.de/dokumentation/terra-x/unser-gruener-planet-wuesten-doku-100.html',
         'info_dict': {
-            'id': '220605_dk_gruener_planet_wuesten_tex',
+            'id': '220525_green_planet_makingof_1_tropen_tex',
+            'ext': 'mp4',
+            'title': 'Making-of Unser grüner Planet - Tropen',
+            'description': 'md5:d7c6949dc7c75c73c4ad51c785fb0b79',
+            'duration': 435.0,
+            'timestamp': 1653811200,
+            'upload_date': '20220529',
+            'format_note': 'hd, main',
+            'thumbnail': 'https://www.zdf.de/assets/unser-gruener-planet-making-of-1-tropen-100~3840x2160?cb=1653493335577',
+            'episode': 'Making-of Unser grüner Planet - Tropen',
+        },
+        'skip': 'No longer available: "Leider kein Video verfügbar"',
+    }, {
+        'url': 'https://www.zdf.de/serien/northern-lights/begegnung-auf-der-bruecke-100.html',
+        'info_dict': {
+            'id': '240319_2310_sendung_not',
             'ext': 'mp4',
-            'title': 'Unser grüner Planet - Wüsten',
-            'description': 'md5:4fc647b6f9c3796eea66f4a0baea2862',
-            'duration': 2613.0,
-            'timestamp': 1654450200,
-            'upload_date': '20220605',
-            'format_note': 'uhd, main',
-            'thumbnail': 'https://www.zdf.de/assets/saguaro-kakteen-102~3840x2160?cb=1655910690796',
+            'title': 'Begegnung auf der Brücke',
+            'description': 'md5:e53a555da87447f7f1207f10353f8e45',
+            'thumbnail': 'https://epg-image.zdf.de/fotobase-webdelivery/images/c5ff1d1f-f5c8-4468-86ac-1b2f1dbecc76?layout=2400x1350',
+            'upload_date': '20250203',
+            'duration': 3083.0,
+            'timestamp': 1738546500,
+            'series_id': '1d7a1879-01ee-4468-8237-c6b4ecd633c7',
+            'series': 'Northern Lights',
+            'season': 'Staffel 1',
+            'season_number': 1,
+            'season_id': '22ac26a2-4ea2-4055-ac0b-98b755cdf718',
+            'episode': 'Begegnung auf der Brücke',
+            'episode_number': 1,
+            'episode_id': 'POS_71049438-024b-471f-b472-4fe2e490d1fb',
         },
     }]
 
@@ -316,12 +355,31 @@ def _extract_entry(self, url, player, content, video_id):
             'timestamp': unified_timestamp(content.get('editorialDate')),
             'thumbnails': thumbnails,
             'chapters': chapters or None,
+            'episode': title,
+            **traverse_obj(content, ('programmeItem', 0, 'http://zdf.de/rels/target', {
+                'series_id': ('http://zdf.de/rels/cmdm/series', 'seriesUuid', {str}),
+                'series': ('http://zdf.de/rels/cmdm/series', 'seriesTitle', {str}),
+                'season': ('http://zdf.de/rels/cmdm/season', 'seasonTitle', {str}),
+                'season_number': ('http://zdf.de/rels/cmdm/season', 'seasonNumber', {int_or_none}),
+                'season_id': ('http://zdf.de/rels/cmdm/season', 'seasonUuid', {str}),
+                'episode_number': ('episodeNumber', {int_or_none}),
+                'episode_id': ('contentId', {str}),
+            })),
         })
 
     def _extract_regular(self, url, player, video_id):
-        content = self._call_api(
-            player['content'], video_id, 'content', player['apiToken'], url)
-        return self._extract_entry(player['content'], player, content, video_id)
+        player_url = player['content']
+
+        try:
+            content = self._call_api(
+                update_url_query(player_url, {'profile': 'player-3'}),
+                video_id, 'content', player['apiToken'], url)
+        except ExtractorError as e:
+            self.report_warning(f'{video_id}: {e.orig_msg}; retrying with v2 profile')
+            content = self._call_api(
+                player_url, video_id, 'content', player['apiToken'], url)
+
+        return self._extract_entry(player_url, player, content, video_id)
 
     def _extract_mobile(self, video_id):
         video = self._download_v2_doc(video_id)
diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py
index ba059babb..ac0629715 100644
--- a/yt_dlp/jsinterp.py
+++ b/yt_dlp/jsinterp.py
@@ -25,7 +25,7 @@ def zeroise(x):
         with contextlib.suppress(TypeError):
             if math.isnan(x):  # NB: NaN cannot be checked by membership
                 return 0
-        return x
+        return int(float(x))
 
     def wrapped(a, b):
         return op(zeroise(a), zeroise(b)) & 0xffffffff
@@ -95,6 +95,61 @@ def _js_ternary(cndn, if_true=True, if_false=False):
     return if_true
 
 
+# Ref: https://es5.github.io/#x9.8.1
+def js_number_to_string(val: float, radix: int = 10):
+    if radix in (JS_Undefined, None):
+        radix = 10
+    assert radix in range(2, 37), 'radix must be an integer at least 2 and no greater than 36'
+
+    if math.isnan(val):
+        return 'NaN'
+    if val == 0:
+        return '0'
+    if math.isinf(val):
+        return '-Infinity' if val < 0 else 'Infinity'
+    if radix == 10:
+        # TODO: implement special cases
+        ...
+
+    ALPHABET = b'0123456789abcdefghijklmnopqrstuvwxyz.-'
+
+    result = collections.deque()
+    sign = val < 0
+    val = abs(val)
+    fraction, integer = math.modf(val)
+    delta = max(math.nextafter(.0, math.inf), math.ulp(val) / 2)
+
+    if fraction >= delta:
+        result.append(-2)  # `.`
+    while fraction >= delta:
+        delta *= radix
+        fraction, digit = math.modf(fraction * radix)
+        result.append(int(digit))
+        # if we need to round, propagate potential carry through fractional part
+        needs_rounding = fraction > 0.5 or (fraction == 0.5 and int(digit) & 1)
+        if needs_rounding and fraction + delta > 1:
+            for index in reversed(range(1, len(result))):
+                if result[index] + 1 < radix:
+                    result[index] += 1
+                    break
+                result.pop()
+
+            else:
+                integer += 1
+            break
+
+    integer, digit = divmod(int(integer), radix)
+    result.appendleft(digit)
+    while integer > 0:
+        integer, digit = divmod(integer, radix)
+        result.appendleft(digit)
+
+    if sign:
+        result.appendleft(-1)  # `-`
+
+    return bytes(ALPHABET[digit] for digit in result).decode('ascii')
+
+
 # Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
 _OPERATORS = {  # None => Defined in JSInterpreter._operator
     '?': None,


Test patch:
diff --git a/test/helper.py b/test/helper.py
index c776e70b7..193019019 100644
--- a/test/helper.py
+++ b/test/helper.py
@@ -237,6 +237,20 @@ def sanitize(key, value):
 
 
 def expect_info_dict(self, got_dict, expected_dict):
+    ALLOWED_KEYS_SORT_ORDER = (
+        # NB: Keep in sync with the docstring of extractor/common.py
+        'id', 'ext', 'direct', 'display_id', 'title', 'alt_title', 'description', 'media_type',
+        'uploader', 'uploader_id', 'uploader_url', 'channel', 'channel_id', 'channel_url', 'channel_is_verified',
+        'channel_follower_count', 'comment_count', 'view_count', 'concurrent_view_count',
+        'like_count', 'dislike_count', 'repost_count', 'average_rating', 'age_limit', 'duration', 'thumbnail', 'heatmap',
+        'chapters', 'chapter', 'chapter_number', 'chapter_id', 'start_time', 'end_time', 'section_start', 'section_end',
+        'categories', 'tags', 'cast', 'composers', 'artists', 'album_artists', 'creators', 'genres',
+        'track', 'track_number', 'track_id', 'album', 'album_type', 'disc_number',
+        'series', 'series_id', 'season', 'season_number', 'season_id', 'episode', 'episode_number', 'episode_id',
+        'timestamp', 'upload_date', 'release_timestamp', 'release_date', 'release_year', 'modified_timestamp', 'modified_date',
+        'playable_in_embed', 'availability', 'live_status', 'location', 'license', '_old_archive_ids',
+    )
+
     expect_dict(self, got_dict, expected_dict)
     # Check for the presence of mandatory fields
     if got_dict.get('_type') not in ('playlist', 'multi_video'):
@@ -252,7 +266,13 @@ def expect_info_dict(self, got_dict, expected_dict):
 
     test_info_dict = sanitize_got_info_dict(got_dict)
 
-    missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
+    # Check for invalid/misspelled field names being returned by the extractor
+    invalid_keys = sorted(test_info_dict.keys() - ALLOWED_KEYS_SORT_ORDER)
+    self.assertFalse(invalid_keys, f'Invalid fields returned by the extractor: {", ".join(invalid_keys)}')
+
+    missing_keys = sorted(
+        test_info_dict.keys() - expected_dict.keys(),
+        key=lambda x: ALLOWED_KEYS_SORT_ORDER.index(x))
     if missing_keys:
         def _repr(v):
             if isinstance(v, str):
diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py
index 06840ed85..ed3ca61c4 100644
--- a/test/test_jsinterp.py
+++ b/test/test_jsinterp.py
@@ -9,7 +9,7 @@
 
 import math
 
-from yt_dlp.jsinterp import JS_Undefined, JSInterpreter
+from yt_dlp.jsinterp import JS_Undefined, JSInterpreter, js_number_to_string
 
 
 class NaN:
@@ -93,6 +93,16 @@ def test_operators(self):
         self._test('function f(){return 0 ?? 42;}', 0)
         self._test('function f(){return "life, the universe and everything" < 42;}', False)
         self._test('function f(){return 0  - 7 * - 6;}', 42)
+        self._test('function f(){return true << "5";}', 32)
+        self._test('function f(){return true << true;}', 2)
+        self._test('function f(){return "19" & "21.9";}', 17)
+        self._test('function f(){return "19" & false;}', 0)
+        self._test('function f(){return "11.0" >> "2.1";}', 2)
+        self._test('function f(){return 5 ^ 9;}', 12)
+        self._test('function f(){return 0.0 << NaN}', 0)
+        self._test('function f(){return null << undefined}', 0)
+        # TODO: Does not work due to number too large
+        # self._test('function f(){return 21 << 4294967297}', 42)
 
     def test_array_access(self):
         self._test('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}', [5, 2, 7])
@@ -431,6 +441,27 @@ def test_slice(self):
         self._test('function f(){return "012345678".slice(-1, 1)}', '')
         self._test('function f(){return "012345678".slice(-3, -1)}', '67')
 
+    def test_js_number_to_string(self):
+        for test, radix, expected in [
+            (0, None, '0'),
+            (-0, None, '0'),
+            (0.0, None, '0'),
+            (-0.0, None, '0'),
+            (math.nan, None, 'NaN'),
+            (-math.nan, None, 'NaN'),
+            (math.inf, None, 'Infinity'),
+            (-math.inf, None, '-Infinity'),
+            (10 ** 21.5, 8, '526665530627250154000000'),
+            (6, 2, '110'),
+            (254, 16, 'fe'),
+            (-10, 2, '-1010'),
+            (-0xff, 2, '-11111111'),
+            (0.1 + 0.2, 16, '0.4cccccccccccd'),
+            (1234.1234, 10, '1234.1234'),
+            # (1000000000000000128, 10, '1000000000000000100')
+        ]:
+            assert js_number_to_string(test, radix) == expected
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py
index 13436f088..7ae627f2c 100644
--- a/test/test_youtube_signature.py
+++ b/test/test_youtube_signature.py
@@ -201,6 +201,10 @@
         'https://www.youtube.com/s/player/2f1832d2/player_ias.vflset/en_US/base.js',
         'YWt1qdbe8SAfkoPHW5d', 'RrRjWQOJmBiP',
     ),
+    (
+        'https://www.youtube.com/s/player/9c6dfc4a/player_ias.vflset/en_US/base.js',
+        'jbu7ylIosQHyJyJV', 'uwI0ESiynAmhNg',
+    ),
 ]