Repository: yt-dlp/yt-dlp
Pull request #7903 (2023-08-19 12:44:08)

[ie/PIAULIZAPortal] Add extractor
**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

-->

### Summary

A very simple extractor for video platform "[ULIZA](https://uliza.jp)" operated by the [PIA Corporation](https://corporate.pia.jp/).
Tickets purchased at "[PIA LIVE STREAM](https://t.pia.jp/streaming)" are used for "ULIZA".

![log of PIA LIVE STREAM](https://image.pia.jp/uploads5/files/1316/1416/4852/info_plslogo.png)

Tests for the following two videos are PASSED:

- [プレゼンテーションプレイヤーのサンプル](https://ulizaportal.jp/pages/005f18b7-e810-5618-cb82-0987c5755d44): A video for testing functionality (with delicious food).
- [【確認用】視聴サンプルページ（ULIZA）](https://ulizaportal.jp/pages/005e1b23-fe93-5780-19a0-98e917cc4b7d?expires=4102412400&signature=f422a993b683e1068f946caf406d211c17d1ef17da8bef3df4a519502155aa91&version=1): A video for testing device and network.

<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*?
- [ ] Fix or improvement to an extractor (Make sure to add/update tests)
- [x] 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))


<!-- Do NOT edit/remove anything below this! -->
</details><details><summary>Copilot Summary</summary>  

<!--
copilot:all
-->
### <samp>🤖 Generated by Copilot at 4987115</samp>

### Summary
🆕🌐🎥

<!--
1.  🆕 - This emoji represents the addition of a new feature or functionality, such as the support for a new website. It can be used to indicate that the change introduces something new and useful to the project.
2.  🌐 - This emoji represents the involvement of a website or a web service, such as the ulizaportal.jp website. It can be used to indicate that the change relates to a specific web domain or platform that hosts media content.
3.  🎥 - This emoji represents the media content that the change deals with, such as live streams and videos. It can be used to indicate that the change enhances the ability of the project to handle different types of media formats and sources.
-->
Add support for ulizaportal.jp, a Japanese media website, by creating a new extractor class `PIAULIZAPortalIE` in `piaulizaportal.py` and importing it in `_extractors.py`.

> _`yt-dlp` expands_
> _Adding `PIAULIZAPortalIE`_
> _Snowy videos_

### Walkthrough
*  Add support for ulizaportal.jp website ([link](https://github.com/yt-dlp/yt-dlp/pull/7903/files?diff=unified&w=0#diff-780b22dc7eb280f5a7b2bbf79aff17826de88ddcbf2fc1116ba19901827aa4e3R1446), [link](https://github.com/yt-dlp/yt-dlp/pull/7903/files?diff=unified&w=0#diff-bffb3538dcbbb34e65f5107a6b873102c7a94122942839a403c3e93d4f297e64R1-R60))
   * Import `PIAULIZAPortalIE` class from `piaulizaportal.py` module in `_extractors.py` ([link](https://github.com/yt-dlp/yt-dlp/pull/7903/files?diff=unified&w=0#diff-780b22dc7eb280f5a7b2bbf79aff17826de88ddcbf2fc1116ba19901827aa4e3R1446))
   * Define `PIAULIZAPortalIE` class in `piaulizaportal.py` module ([link](https://github.com/yt-dlp/yt-dlp/pull/7903/files?diff=unified&w=0#diff-bffb3538dcbbb34e65f5107a6b873102c7a94122942839a403c3e93d4f297e64R1-R60))



</details>


pzhlkj6612: Hold on, the logic for checking "live_status" may be wrong.

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a8587fe92..90e7faf7c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -217,7 +217,7 @@ ## Adding support for a new site
 1. Add an import in [`yt_dlp/extractor/_extractors.py`](yt_dlp/extractor/_extractors.py). Note that the class name must end with `IE`.
 1. Run `python test/test_download.py TestDownload.test_YourExtractor` (note that `YourExtractor` doesn't end with `IE`). This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, the tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note that tests with `only_matching` key in test's dict are not counted in. You can also run all the tests in one go with `TestDownload.test_YourExtractor_all`
 1. Make sure you have atleast one test for your extractor. Even if all videos covered by the extractor are expected to be inaccessible for automated testing, tests should still be added with a `skip` parameter indicating why the particular test is disabled from running.
-1. Have a look at [`yt_dlp/extractor/common.py`](yt_dlp/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](yt_dlp/extractor/common.py#L91-L426). Add tests and code for as many as you want.
+1. Have a look at [`yt_dlp/extractor/common.py`](yt_dlp/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](yt_dlp/extractor/common.py#L119-L440). Add tests and code for as many as you want.
 1. Make sure your code follows [yt-dlp coding conventions](#yt-dlp-coding-conventions) and check the code with [flake8](https://flake8.pycqa.org/en/latest/index.html#quickstart):
 
         $ flake8 yt_dlp/extractor/yourextractor.py
@@ -251,7 +251,7 @@ ## yt-dlp coding conventions
 
 ### Mandatory and optional metafields
 
-For extraction to work yt-dlp relies on metadata your extractor extracts and provides to yt-dlp expressed by an [information dictionary](yt_dlp/extractor/common.py#L91-L426) or simply *info dict*. Only the following meta fields in the *info dict* are considered mandatory for a successful extraction process by yt-dlp:
+For extraction to work yt-dlp relies on metadata your extractor extracts and provides to yt-dlp expressed by an [information dictionary](yt_dlp/extractor/common.py#L119-L440) or simply *info dict*. Only the following meta fields in the *info dict* are considered mandatory for a successful extraction process by yt-dlp:
 
  - `id` (media identifier)
  - `title` (media title)
@@ -696,7 +696,7 @@ #### Examples
 
 ### Use convenience conversion and parsing functions
 
-Wrap all extracted numeric data into safe functions from [`yt_dlp/utils.py`](yt_dlp/utils.py): `int_or_none`, `float_or_none`. Use them for string to number conversions as well.
+Wrap all extracted numeric data into safe functions from [`yt_dlp/utils/`](yt_dlp/utils/): `int_or_none`, `float_or_none`. Use them for string to number conversions as well.
 
 Use `url_or_none` for safe URL processing.
 
@@ -704,7 +704,7 @@ ### Use convenience conversion and parsing functions
 
 Use `unified_strdate` for uniform `upload_date` or any `YYYYMMDD` meta field extraction, `unified_timestamp` for uniform `timestamp` extraction, `parse_filesize` for `filesize` extraction, `parse_count` for count meta fields extraction, `parse_resolution`, `parse_duration` for `duration` extraction, `parse_age_limit` for `age_limit` extraction. 
 
-Explore [`yt_dlp/utils.py`](yt_dlp/utils.py) for more useful convenience functions.
+Explore [`yt_dlp/utils/`](yt_dlp/utils/) for more useful convenience functions.
 
 #### Examples
 
diff --git a/README.md b/README.md
index d94d8ea82..d9b11952d 100644
--- a/README.md
+++ b/README.md
@@ -1800,7 +1800,7 @@ # EXTRACTOR ARGUMENTS
 #### youtube
 * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes
 * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
-* `player_client`: Clients to extract video data from. The main clients are `web`, `android` and `ios` with variants `_music`, `_embedded`, `_embedscreen`, `_creator` (e.g. `web_embedded`); and `mweb` and `tv_embedded` (agegate bypass) with no variants. By default, `ios,android,web` is used, but `tv_embedded` and `creator` variants are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. You can use `all` to use all the clients, and `default` for the default clients.
+* `player_client`: Clients to extract video data from. The main clients are `web`, `android` and `ios` with variants `_music`, `_embedded`, `_embedscreen`, `_creator` (e.g. `web_embedded`); and `mweb`, `mweb_embedscreen` and `tv_embedded` (agegate bypass) with no variants. By default, `ios,android,web` is used, but `tv_embedded` and `creator` variants are added as required for age-gated videos. Similarly, the music variants are added for `music.youtube.com` urls. You can use `all` to use all the clients, and `default` for the default clients.
 * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
 * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
 * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
diff --git a/devscripts/make_changelog.py b/devscripts/make_changelog.py
index ac68dcd19..9ff65db14 100644
--- a/devscripts/make_changelog.py
+++ b/devscripts/make_changelog.py
@@ -260,7 +260,7 @@ class CommitRange:
     AUTHOR_INDICATOR_RE = re.compile(r'Authored by:? ', re.IGNORECASE)
     MESSAGE_RE = re.compile(r'''
         (?:\[(?P<prefix>[^\]]+)\]\ )?
-        (?:(?P<sub_details>`?[^:`]+`?): )?
+        (?:(?P<sub_details>`?[\w.-]+`?): )?
         (?P<message>.+?)
         (?:\ \((?P<issues>\#\d+(?:,\ \#\d+)*)\))?
         ''', re.VERBOSE | re.DOTALL)
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 1feed3052..39aaf2c2e 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -239,9 +239,9 @@ class YoutubeDL:
                        'selected' (check selected formats),
                        or None (check only if requested by extractor)
     paths:             Dictionary of output paths. The allowed keys are 'home'
-                       'temp' and the keys of OUTTMPL_TYPES (in utils.py)
+                       'temp' and the keys of OUTTMPL_TYPES (in utils/_utils.py)
     outtmpl:           Dictionary of templates for output names. Allowed keys
-                       are 'default' and the keys of OUTTMPL_TYPES (in utils.py).
+                       are 'default' and the keys of OUTTMPL_TYPES (in utils/_utils.py).
                        For compatibility with youtube-dl, a single string can also be used
     outtmpl_na_placeholder: Placeholder for unavailable meta fields.
     restrictfilenames: Do not allow "&" and spaces in file names
@@ -422,7 +422,7 @@ class YoutubeDL:
                          asked whether to download the video.
                        - Raise utils.DownloadCancelled(msg) to abort remaining
                          downloads when a video is rejected.
-                       match_filter_func in utils.py is one example for this.
+                       match_filter_func in utils/_utils.py is one example for this.
     color:             A Dictionary with output stream names as keys
                        and their respective color policy as values.
                        Can also just be a single color policy,
diff --git a/yt_dlp/compat/urllib/__init__.py b/yt_dlp/compat/urllib/__init__.py
index b27cc6133..9084b3c2b 100644
--- a/yt_dlp/compat/urllib/__init__.py
+++ b/yt_dlp/compat/urllib/__init__.py
@@ -1,7 +1,7 @@
 # flake8: noqa: F405
 from urllib import *  # noqa: F403
 
-del request
+del request  # noqa: F821
 from . import request  # noqa: F401
 
 from ..compat_utils import passthrough_module
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index 9cda06d8f..49c35cf71 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -1126,6 +1126,7 @@
     MofosexEmbedIE,
 )
 from .mojvideo import MojvideoIE
+from .monstercat import MonstercatIE
 from .morningstar import MorningstarIE
 from .motherless import (
     MotherlessIE,
@@ -1451,6 +1452,7 @@
 from .phoenix import PhoenixIE
 from .photobucket import PhotobucketIE
 from .piapro import PiaproIE
+from .piaulizaportal import PIAULIZAPortalIE
 from .picarto import (
     PicartoIE,
     PicartoVodIE,
diff --git a/yt_dlp/extractor/abc.py b/yt_dlp/extractor/abc.py
index f56133eb3..d2cf5f7c5 100644
--- a/yt_dlp/extractor/abc.py
+++ b/yt_dlp/extractor/abc.py
@@ -180,7 +180,6 @@ class ABCIViewIE(InfoExtractor):
     _VALID_URL = r'https?://iview\.abc\.net\.au/(?:[^/]+/)*video/(?P<id>[^/?#]+)'
     _GEO_COUNTRIES = ['AU']
 
-    # ABC iview programs are normally available for 14 days only.
     _TESTS = [{
         'url': 'https://iview.abc.net.au/show/gruen/series/11/video/LE1927H001S00',
         'md5': '67715ce3c78426b11ba167d875ac6abf',
diff --git a/yt_dlp/extractor/arte.py b/yt_dlp/extractor/arte.py
index e3cc5afb0..a19cd2a3a 100644
--- a/yt_dlp/extractor/arte.py
+++ b/yt_dlp/extractor/arte.py
@@ -169,7 +169,7 @@ def _real_extract(self, url):
                 )))
 
             short_label = traverse_obj(stream_version, 'shortLabel', expected_type=str, default='?')
-            if stream['protocol'].startswith('HLS'):
+            if 'HLS' in stream['protocol']:
                 fmts, subs = self._extract_m3u8_formats_and_subtitles(
                     stream['url'], video_id=video_id, ext='mp4', m3u8_id=stream_version_code, fatal=False)
                 for fmt in fmts:
diff --git a/yt_dlp/extractor/ign.py b/yt_dlp/extractor/ign.py
index 64875f8ce..1c4f105e9 100644
--- a/yt_dlp/extractor/ign.py
+++ b/yt_dlp/extractor/ign.py
@@ -197,10 +197,6 @@ class IGNVideoIE(IGNBaseIE):
             'thumbnail': 'https://sm.ign.com/ign_me/video/h/how-hitman/how-hitman-aims-to-be-different-than-every-other-s_8z14.jpg',
             'duration': 298,
             'tags': 'count:13',
-            'display_id': '112203',
-            'thumbnail': 'https://sm.ign.com/ign_me/video/h/how-hitman/how-hitman-aims-to-be-different-than-every-other-s_8z14.jpg',
-            'duration': 298,
-            'tags': 'count:13',
         },
         'expected_warnings': ['HTTP Error 400: Bad Request'],
     }, {
diff --git a/yt_dlp/extractor/monstercat.py b/yt_dlp/extractor/monstercat.py
new file mode 100644
index 000000000..7f04825fc
--- /dev/null
+++ b/yt_dlp/extractor/monstercat.py
@@ -0,0 +1,79 @@
+import re
+
+from .common import InfoExtractor
+from ..utils import (
+    clean_html,
+    extract_attributes,
+    get_element_by_class,
+    get_element_html_by_class,
+    get_element_text_and_html_by_tag,
+    int_or_none,
+    unified_strdate,
+    strip_or_none,
+    traverse_obj,
+    try_call,
+)
+
+
+class MonstercatIE(InfoExtractor):
+    _VALID_URL = r'https://www\.monstercat\.com/release/(?P<id>\d+)'
+    _TESTS = [{
+        'url': 'https://www.monstercat.com/release/742779548009',
+        'playlist_count': 20,
+        'info_dict': {
+            'title': 'The Secret Language of Trees',
+            'id': '742779548009',
+            'thumbnail': 'https://www.monstercat.com/release/742779548009/cover',
+            'release_year': 2023,
+            'release_date': '20230711',
+            'album': 'The Secret Language of Trees',
+            'album_artist': 'BT',
+        }
+    }]
+
+    def _extract_tracks(self, table, album_meta):
+        for td in re.findall(r'<tr[^<]*>((?:(?!</tr>)[\w\W])+)', table):  # regex by chatgpt due to lack of get_elements_by_tag
+            title = clean_html(try_call(
+                lambda: get_element_by_class('d-inline-flex flex-column', td).partition(' <span')[0]))
+            ids = extract_attributes(try_call(lambda: get_element_html_by_class('btn-play cursor-pointer mr-small', td)) or '')
+            track_id = ids.get('data-track-id')
+            release_id = ids.get('data-release-id')
+
+            track_number = int_or_none(try_call(lambda: get_element_by_class('py-xsmall', td)))
+            if not track_id or not release_id:
+                self.report_warning(f'Skipping track {track_number}, ID(s) not found')
+                self.write_debug(f'release_id={repr(release_id)} track_id={repr(track_id)}')
+                continue
+            yield {
+                **album_meta,
+                'title': title,
+                'track': title,
+                'track_number': track_number,
+                'artist': clean_html(try_call(lambda: get_element_by_class('d-block fs-xxsmall', td))),
+                'url': f'https://www.monstercat.com/api/release/{release_id}/track-stream/{track_id}',
+                'id': track_id,
+                'ext': 'mp3'
+            }
+
+    def _real_extract(self, url):
+        url_id = self._match_id(url)
+        html = self._download_webpage(url, url_id)
+        # wrap all `get_elements` in `try_call`, HTMLParser has problems with site's html
+        tracklist_table = try_call(lambda: get_element_by_class('table table-small', html)) or ''
+
+        title = try_call(lambda: get_element_text_and_html_by_tag('h1', html)[0])
+        date = traverse_obj(html, ({lambda html: get_element_by_class('font-italic mb-medium d-tablet-none d-phone-block',
+                            html).partition('Released ')}, 2, {strip_or_none}, {unified_strdate}))
+
+        album_meta = {
+            'title': title,
+            'album': title,
+            'thumbnail': f'https://www.monstercat.com/release/{url_id}/cover',
+            'album_artist': try_call(
+                lambda: get_element_by_class('h-normal text-uppercase mb-desktop-medium mb-smallish', html)),
+            'release_year': int_or_none(date[:4]) if date else None,
+            'release_date': date,
+        }
+
+        return self.playlist_result(
+            self._extract_tracks(tracklist_table, album_meta), playlist_id=url_id, **album_meta)
diff --git a/yt_dlp/extractor/nebula.py b/yt_dlp/extractor/nebula.py
index 4f3e691b7..8fba2bcf7 100644
--- a/yt_dlp/extractor/nebula.py
+++ b/yt_dlp/extractor/nebula.py
@@ -127,7 +127,6 @@ class NebulaIE(NebulaBaseIE):
                 'channel_id': 'lindsayellis',
                 'uploader': 'Lindsay Ellis',
                 'uploader_id': 'lindsayellis',
-                'timestamp': 1533009600,
                 'uploader_url': 'https://nebula.tv/lindsayellis',
                 'series': 'Lindsay Ellis',
                 'display_id': 'that-time-disney-remade-beauty-and-the-beast',
diff --git a/yt_dlp/extractor/peekvids.py b/yt_dlp/extractor/peekvids.py
index d1fc058b9..41f591b09 100644
--- a/yt_dlp/extractor/peekvids.py
+++ b/yt_dlp/extractor/peekvids.py
@@ -146,7 +146,6 @@ class PlayVidsIE(PeekVidsBaseIE):
             'uploader': 'Brazzers',
             'age_limit': 18,
             'view_count': int,
-            'age_limit': 18,
             'categories': list,
             'tags': list,
         },
diff --git a/yt_dlp/extractor/piaulizaportal.py b/yt_dlp/extractor/piaulizaportal.py
new file mode 100644
index 000000000..1eb6d92b7
--- /dev/null
+++ b/yt_dlp/extractor/piaulizaportal.py
@@ -0,0 +1,70 @@
+from .common import InfoExtractor
+from ..utils import (
+    ExtractorError,
+    int_or_none,
+    parse_qs,
+    time_seconds,
+    traverse_obj,
+)
+
+
+class PIAULIZAPortalIE(InfoExtractor):
+    IE_DESC = 'ulizaportal.jp - PIA LIVE STREAM'
+    _VALID_URL = r'https?://(?:www\.)?ulizaportal\.jp/pages/(?P<id>[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12})'
+    _TESTS = [{
+        'url': 'https://ulizaportal.jp/pages/005f18b7-e810-5618-cb82-0987c5755d44',
+        'info_dict': {
+            'id': '005f18b7-e810-5618-cb82-0987c5755d44',
+            'title': 'プレゼンテーションプレイヤーのサンプル',
+            'live_status': 'not_live',
+        },
+        'params': {
+            'skip_download': True,
+            'ignore_no_formats_error': True,
+        },
+    }, {
+        'url': 'https://ulizaportal.jp/pages/005e1b23-fe93-5780-19a0-98e917cc4b7d?expires=4102412400&signature=f422a993b683e1068f946caf406d211c17d1ef17da8bef3df4a519502155aa91&version=1',
+        'info_dict': {
+            'id': '005e1b23-fe93-5780-19a0-98e917cc4b7d',
+            'title': '【確認用】視聴サンプルページ（ULIZA）',
+            'live_status': 'not_live',
+        },
+        'params': {
+            'skip_download': True,
+            'ignore_no_formats_error': True,
+        },
+    }]
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+
+        expires = int_or_none(traverse_obj(parse_qs(url), ('expires', 0)))
+        if expires and expires <= time_seconds():
+            raise ExtractorError('The link is expired.', video_id=video_id, expected=True)
+
+        webpage = self._download_webpage(url, video_id)
+
+        player_data = self._download_webpage(
+            self._search_regex(
+                r'<script [^>]*\bsrc="(https://player-api\.p\.uliza\.jp/v1/players/[^"]+)"',
+                webpage, 'player data url'),
+            video_id, headers={'Referer': 'https://ulizaportal.jp/'},
+            note='Fetching player data', errnote='Unable to fetch player data')
+
+        formats = self._extract_m3u8_formats(
+            self._search_regex(
+                r'["\'](https://vms-api\.p\.uliza\.jp/v1/prog-index\.m3u8[^"\']+)', player_data,
+                'm3u8 url', default=None),
+            video_id, fatal=False)
+        m3u8_type = self._search_regex(
+            r'/hls/(dvr|video)/', traverse_obj(formats, (0, 'url')), 'm3u8 type', default=None)
+
+        return {
+            'id': video_id,
+            'title': self._html_extract_title(webpage),
+            'formats': formats,
+            'live_status': {
+                'video': 'is_live',
+                'dvr': 'was_live',  # short-term archives
+            }.get(m3u8_type, 'not_live'),  # VOD or long-term archives
+        }
diff --git a/yt_dlp/extractor/radiofrance.py b/yt_dlp/extractor/radiofrance.py
index 35f4b91dd..ec1b97631 100644
--- a/yt_dlp/extractor/radiofrance.py
+++ b/yt_dlp/extractor/radiofrance.py
@@ -82,7 +82,7 @@ class RadioFranceBaseIE(InfoExtractor):
     def _extract_data_from_webpage(self, webpage, display_id, key):
         return traverse_obj(self._search_json(
             r'\bconst\s+data\s*=', webpage, key, display_id,
-            contains_pattern=r'(\[\{.*?\}\]);', transform_source=js_to_json),
+            contains_pattern=r'\[\{(?s:.+)\}\]', transform_source=js_to_json),
             (..., 'data', key, {dict}), get_all=False) or {}
 
 
diff --git a/yt_dlp/extractor/rcs.py b/yt_dlp/extractor/rcs.py
index 028d3d90b..b865f63fb 100644
--- a/yt_dlp/extractor/rcs.py
+++ b/yt_dlp/extractor/rcs.py
@@ -239,10 +239,10 @@ class RCSEmbedsIE(RCSBaseIE):
         }
     }, {
         'url': 'https://video.gazzanet.gazzetta.it/video-embed/gazzanet-mo05-0000260789',
-        'match_only': True
+        'only_matching': True
     }, {
         'url': 'https://video.gazzetta.it/video-embed/49612410-00ca-11eb-bcd8-30d4253e0140',
-        'match_only': True
+        'only_matching': True
     }]
     _WEBPAGE_TESTS = [{
         'url': 'https://www.iodonna.it/video-iodonna/personaggi-video/monica-bellucci-piu-del-lavoro-oggi-per-me-sono-importanti-lamicizia-e-la-famiglia/',
@@ -325,7 +325,7 @@ class RCSIE(RCSBaseIE):
         }
     }, {
         'url': 'https://video.corriere.it/video-360/metro-copenaghen-tutta-italiana/a248a7f0-e2db-11e9-9830-af2de6b1f945',
-        'match_only': True
+        'only_matching': True
     }]
 
 
diff --git a/yt_dlp/extractor/rokfin.py b/yt_dlp/extractor/rokfin.py
index 4a4d40bef..cad76f0c9 100644
--- a/yt_dlp/extractor/rokfin.py
+++ b/yt_dlp/extractor/rokfin.py
@@ -40,7 +40,6 @@ class RokfinIE(InfoExtractor):
             'channel': 'Jimmy Dore',
             'channel_id': 65429,
             'channel_url': 'https://rokfin.com/TheJimmyDoreShow',
-            'duration': 213.0,
             'availability': 'public',
             'live_status': 'not_live',
             'dislike_count': int,
diff --git a/yt_dlp/extractor/s4c.py b/yt_dlp/extractor/s4c.py
index 990ea2b44..67eff723b 100644
--- a/yt_dlp/extractor/s4c.py
+++ b/yt_dlp/extractor/s4c.py
@@ -78,7 +78,6 @@ class S4CSeriesIE(InfoExtractor):
         'info_dict': {
             'id': '864982911',
             'title': 'Iaith ar Daith',
-            'description': 'md5:e878ebf660dce89bd2ef521d7ce06397'
         },
     }, {
         'url': 'https://www.s4c.cymru/clic/series/866852587',
@@ -86,7 +85,6 @@ class S4CSeriesIE(InfoExtractor):
         'info_dict': {
             'id': '866852587',
             'title': 'FFIT Cymru',
-            'description': 'md5:abcb3c129cb68dbb6cd304fd33b07e96'
         },
     }]
 
diff --git a/yt_dlp/extractor/sovietscloset.py b/yt_dlp/extractor/sovietscloset.py
index 453016ccb..493eea2a6 100644
--- a/yt_dlp/extractor/sovietscloset.py
+++ b/yt_dlp/extractor/sovietscloset.py
@@ -76,7 +76,6 @@ class SovietsClosetIE(SovietsClosetBaseIE):
                 'title': 'Arma 3 - Zeus Games #5',
                 'uploader': 'SovietWomble',
                 'thumbnail': r're:^https?://.*\.b-cdn\.net/c0e5e76f-3a93-40b4-bf01-12343c2eec5d/thumbnail\.jpg$',
-                'uploader': 'SovietWomble',
                 'creator': 'SovietWomble',
                 'release_timestamp': 1461157200,
                 'release_date': '20160420',
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index 023d8fd8c..a39d17cf1 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -902,7 +902,7 @@ def extract_relative_time(relative_time_text):
         e.g. 'streamed 6 days ago', '5 seconds ago (edited)', 'updated today', '8 yr ago'
         """
 
-        # XXX: this could be moved to a general function in utils.py
+        # XXX: this could be moved to a general function in utils/_utils.py
         # The relative time text strings are roughly the same as what
         # Javascript's Intl.RelativeTimeFormat function generates.
         # See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
diff --git a/yt_dlp/networking/__init__.py b/yt_dlp/networking/__init__.py
index 5e8876484..5b1599a6d 100644
--- a/yt_dlp/networking/__init__.py
+++ b/yt_dlp/networking/__init__.py
@@ -1,4 +1,4 @@
-# flake8: noqa: 401
+# flake8: noqa: F401
 from .common import (
     HEADRequest,
     PUTRequest,
diff --git a/yt_dlp/networking/_urllib.py b/yt_dlp/networking/_urllib.py
index c327f7744..9e2bf33e4 100644
--- a/yt_dlp/networking/_urllib.py
+++ b/yt_dlp/networking/_urllib.py
@@ -337,7 +337,7 @@ def handle_sslerror(e: ssl.SSLError):
 
 def handle_response_read_exceptions(e):
     if isinstance(e, http.client.IncompleteRead):
-        raise IncompleteRead(partial=e.partial, cause=e, expected=e.expected) from e
+        raise IncompleteRead(partial=len(e.partial), cause=e, expected=e.expected) from e
     elif isinstance(e, ssl.SSLError):
         handle_sslerror(e)
     elif isinstance(e, (OSError, EOFError, http.client.HTTPException, *CONTENT_DECODE_ERRORS)):
diff --git a/yt_dlp/networking/exceptions.py b/yt_dlp/networking/exceptions.py
index 465b18ba9..f58dc246e 100644
--- a/yt_dlp/networking/exceptions.py
+++ b/yt_dlp/networking/exceptions.py
@@ -75,10 +75,10 @@ def __repr__(self):
 
 
 class IncompleteRead(TransportError):
-    def __init__(self, partial, expected=None, **kwargs):
+    def __init__(self, partial: int, expected: int = None, **kwargs):
         self.partial = partial
         self.expected = expected
-        msg = f'{len(partial)} bytes read'
+        msg = f'{partial} bytes read'
         if expected is not None:
             msg += f', {expected} more expected'
 


Test patch:
diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py
index 3cfb61fb2..916ee48b9 100644
--- a/test/test_YoutubeDL.py
+++ b/test/test_YoutubeDL.py
@@ -631,7 +631,6 @@ def test_add_extra_info(self):
         self.assertEqual(test_dict['playlist'], 'funny videos')
 
     outtmpl_info = {
-        'id': '1234',
         'id': '1234',
         'ext': 'mp4',
         'width': None,
diff --git a/test/test_networking_utils.py b/test/test_networking_utils.py
index dbf656090..419aae1e4 100644
--- a/test/test_networking_utils.py
+++ b/test/test_networking_utils.py
@@ -269,14 +269,14 @@ def test_compat_http_error_autoclose(self):
         assert not response.closed
 
     def test_incomplete_read_error(self):
-        error = IncompleteRead(b'test', 3, cause='test')
+        error = IncompleteRead(4, 3, cause='test')
         assert isinstance(error, IncompleteRead)
         assert repr(error) == '<IncompleteRead: 4 bytes read, 3 more expected>'
         assert str(error) == error.msg == '4 bytes read, 3 more expected'
-        assert error.partial == b'test'
+        assert error.partial == 4
         assert error.expected == 3
         assert error.cause == 'test'
 
-        error = IncompleteRead(b'aaa')
+        error = IncompleteRead(3)
         assert repr(error) == '<IncompleteRead: 3 bytes read>'
         assert str(error) == '3 bytes read'