Repository: yt-dlp/yt-dlp

Issue:
[NSFW]Requesting support for Jable.tv
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2022.03.08.1**. ([update instructions](https://github.com/yt-dlp/yt-dlp#update))
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] 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

### Region

Global

### Example URLs

-Example Video: https://jable.tv/videos/pfes-055/

### Description

Jable.tv is a porn site with numerous Japanese AV.
Please add support for it, thank you very much. 

### Verbose log

```shell
yt-dlp --write-subs -vU https://jable.tv/videos/pfes-055/
[debug] Command-line config: ['--write-subs', '-vU', 'https://jable.tv/videos/pfes-055/']
[debug] Encodings: locale cp950, fs utf-8, out utf-8, err utf-8, pref cp950
[debug] yt-dlp version 2022.03.08.1 [c0c2c57] (win_exe)
[debug] Python version 3.8.10 (CPython 64bit) - Windows-10-10.0.19043-SP0
[debug] exe versions: ffmpeg 4.4-full_build-www.gyan.dev (setts)
[debug] Optional libraries: brotli, Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2022.03.08.1, Current version: 2022.03.08.1
yt-dlp is up to date (2022.03.08.1)
[debug] [generic] Extracting URL: https://jable.tv/videos/pfes-055/
[generic] pfes-055: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] pfes-055: Downloading webpage
[generic] pfes-055: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://jable.tv/videos/pfes-055/
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1389, in wrapper
  File "yt_dlp\YoutubeDL.py", line 1459, in __extract_info
  File "yt_dlp\extractor\common.py", line 617, in extract
  File "yt_dlp\extractor\generic.py", line 3985, in _real_extract
yt_dlp.utils.UnsupportedError: Unsupported URL: https://jable.tv/videos/pfes-055/
```



Discussion:

Resolving patch:
diff --git a/yt_dlp/extractor/extractors.py b/yt_dlp/extractor/extractors.py
--- a/yt_dlp/extractor/extractors.py
+++ b/yt_dlp/extractor/extractors.py
@@ -701,6 +701,10 @@
 from .ivideon import IvideonIE
 from .iwara import IwaraIE
 from .izlesene import IzleseneIE
+from .jable import (
+    JableIE,
+    JablePlaylistIE,
+)
 from .jamendo import (
     JamendoIE,
     JamendoAlbumIE,
diff --git a/yt_dlp/extractor/jable.py b/yt_dlp/extractor/jable.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/jable.py
@@ -0,0 +1,107 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import re
+
+from .common import InfoExtractor
+from ..utils import (
+    InAdvancePagedList,
+    int_or_none,
+    orderedSet,
+    unified_strdate,
+)
+
+
+class JableIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?jable.tv/videos/(?P<id>[\w-]+)'
+    _TESTS = [{
+        'url': 'https://jable.tv/videos/pppd-812/',
+        'md5': 'f1537283a9bc073c31ff86ca35d9b2a6',
+        'info_dict': {
+            'id': 'pppd-812',
+            'ext': 'mp4',
+            'title': 'PPPD-812 只要表現好巨乳女教師吉根柚莉愛就獎勵學生們在白虎穴內射出精液',
+            'description': 'md5:5b6d4199a854f62c5e56e26ccad19967',
+            'thumbnail': r're:^https?://.*\.jpg$',
+            'age_limit': 18,
+            'like_count': int,
+            'view_count': int,
+        },
+    }, {
+        'url': 'https://jable.tv/videos/apak-220/',
+        'md5': '71f9239d69ced58ab74a816908847cc1',
+        'info_dict': {
+            'id': 'apak-220',
+            'ext': 'mp4',
+            'title': 'md5:5c3861b7cf80112a6e2b70bccf170824',
+            'description': '',
+            'thumbnail': r're:^https?://.*\.jpg$',
+            'age_limit': 18,
+            'like_count': int,
+            'view_count': int,
+            'upload_date': '20220319',
+        },
+    }]
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        webpage = self._download_webpage(url, video_id)
+        formats = self._extract_m3u8_formats(
+            self._search_regex(r'var\s+hlsUrl\s*=\s*\'([^\']+)', webpage, 'hls_url'), video_id, 'mp4', m3u8_id='hls')
+        self._sort_formats(formats)
+
+        return {
+            'id': video_id,
+            'title': self._og_search_title(webpage),
+            'description': self._og_search_description(webpage, default=''),
+            'thumbnail': self._og_search_thumbnail(webpage, default=None),
+            'formats': formats,
+            'age_limit': 18,
+            'upload_date': unified_strdate(self._search_regex(
+                r'class="inactive-color">\D+\s+(\d{4}-\d+-\d+)', webpage, 'upload_date', default=None)),
+            'view_count': int_or_none(self._search_regex(
+                r'#icon-eye"></use></svg>\n*<span class="mr-3">([\d ]+)',
+                webpage, 'view_count', default='').replace(' ', '')),
+            'like_count': int_or_none(self._search_regex(
+                r'#icon-heart"></use></svg><span class="count">(\d+)', webpage, 'link_count', default=None)),
+        }
+
+
+class JablePlaylistIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?jable.tv/(?:categories|models|tags)/(?P<id>[\w-]+)'
+    _TESTS = [{
+        'url': 'https://jable.tv/models/kaede-karen/',
+        'info_dict': {
+            'id': 'kaede-karen',
+            'title': '楓カレン',
+        },
+        'playlist_count': 34,
+    }, {
+        'url': 'https://jable.tv/categories/roleplay/',
+        'only_matching': True,
+    }, {
+        'url': 'https://jable.tv/tags/girl/',
+        'only_matching': True,
+    }]
+
+    def _real_extract(self, url):
+        playlist_id = self._match_id(url)
+        webpage = self._download_webpage(url, playlist_id)
+
+        def page_func(page_num):
+            return [
+                self.url_result(player_url, JableIE)
+                for player_url in orderedSet(re.findall(
+                    r'href="(https://jable.tv/videos/[\w-]+/?)"',
+                    self._download_webpage(url, playlist_id, query={
+                        'mode': 'async',
+                        'from': page_num + 1,
+                        'function': 'get_block',
+                        'block_id': 'list_videos_common_videos_list',
+                    }, note=f'Downloading page {page_num + 1}')))]
+
+        return self.playlist_result(
+            InAdvancePagedList(page_func, int_or_none(self._search_regex(
+                r'from:(\d+)">[^<]+\s*&raquo;', webpage, 'last page number', default=1)), 24),
+            playlist_id, self._search_regex(
+                r'<h2 class="h3-md mb-1">([^<]+)', webpage, 'playlist title', default=None))


Test patch: