Repository: yt-dlp/yt-dlp

Issue:
Site support request for: ixigua.com
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2022.02.04**. ([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

Thailand, Mainland China, probably worldwide.

### Example URLs

https://www.ixigua.com/6996881461559165471
https://www.ixigua.com/6901922393657180679?id=6963688388327113255&logTag=c159ae59d579c199c066

### Description

Xigua Video (https://www.ixigua.com/) is an online video-sharing platform owned by ByteDance. As of June 2020, the platform has 131 million monthly active users.

### Verbose log

```shell
[debug] Command-line config: ['-vU', 'https://www.ixigua.com/6996881461559165471']
[debug] Encodings: locale cp874, fs utf-8, out utf-8, err utf-8, pref cp874
[debug] yt-dlp version 2022.02.04 [c1653e9ef]
[debug] Python version 3.7.2 (CPython 64bit) - Windows-10-10.0.18362-SP0
[debug] exe versions: ffmpeg 2021-10-28-git-e84c83ef98-full_build-www.gyan.dev (setts), ffprobe 2021-10-28-git-e84c83ef98-full_build-www.gyan.dev
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2022.02.04, Current version: 2022.02.04
yt-dlp is up to date (2022.02.04)
[debug] [generic] Extracting URL: https://www.ixigua.com/6996881461559165471
[generic] 6996881461559165471: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] 6996881461559165471: Downloading webpage
[generic] 6996881461559165471: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://www.ixigua.com/6996881461559165471
Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\yt_dlp\YoutubeDL.py", line 1381, in wrapper
    return func(self, *args, **kwargs)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\yt_dlp\YoutubeDL.py", line 1451, in __extract_info
    ie_result = ie.extract(url)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\yt_dlp\extractor\common.py", line 612, in extract
    ie_result = self._real_extract(url)
  File "C:\Users\User\AppData\Local\Programs\Python\Python37\lib\site-packages\yt_dlp\extractor\generic.py", line 3986, in _real_extract
    raise UnsupportedError(url)
yt_dlp.utils.UnsupportedError: Unsupported URL: https://www.ixigua.com/6996881461559165471


C:\Users\User>
```



Discussion:
I found the video actually located at `<script id=SSR_HYDRATED_DATA>`. The video url list in `[anyVideo][gidInformation][packerData][videoResource]`. 

The videoResource give url based on video type 

The json will be look like this:
```
videoResource": {
        "vid": "v0d004g10000c4d1t7jc77ub4g3o88b0",
        "dash": {
            ...
            }],
            "dynamic_video": {
                "dynamic_type": "segment_base",
                "dynamic_video_list": [{
                "definition": "360p",
                "quality": "normal",
                "vtype": "dash",
                "vwidth": 640,
                "vheight": 360,
                "bitrate": 639696,
                "real_bitrate": 571614,
                "fps": 30,
                "size": 73601070,
                "codec_type": "h264",
                "file_hash": "6aa62e53e04e1c46a94b33143fc9705f",
                "main_url": "aHR0cDovL3Y5LXhnLXdlYi1kZWZhdWx0Lml4aWd1YS5jb20vOWQ5ZjljNjFiMDFhMDEwMWQ5MmYxM2ExNzYyYjIyOGEvNjI5ODFmN2EvdmlkZW8vdG9zL2NuL3Rvcy1jbi1vLTAwMDQvODNjNDZmYWY1ODhlNDFjMzk4MmI3NTU3Y2Y1MDc2ODAvbWVkaWEtdmlkZW8tYXZjMS8\u002FYT0xNzY4JmNoPTAmY3I9MCZkcj0wJmVyPTAmbHI9ZGVmYXVsdCZjZD0wJTdDMCU3QzAlN0MwJmN2PTEmYnI9NTU4JmJ0PTU1OCZjcz0wJmRzPTEmbWltZV90eXBlPXZpZGVvX21wNCZxcz0wJnJjPVBEYzZPelZsTkRWcE9EZzNaVFUwUEVCcE16dHlhbVU2WmpvME56TXpORGN6TTBBd1h6WXhNelZnTmpVeExWNWdYakZlWVNOZk5UQXhjalJuY1dGZ0xTMWtMV0Z6Y3clM0QlM0QmbD0yMDIyMDYwMjA5MDc0ODAxMDE1MTA2MzE1NjA1N0ZGQTlG",
                "backup_url_1": "aHR0cDovL3Y5LXhnLXdlYi1kZWZhdWx0Lml4aWd1YS5jb20vOWQ5ZjljNjFiMDFhMDEwMWQ5MmYxM2ExNzYyYjIyOGEvNjI5ODFmN2EvdmlkZW8vdG9zL2NuL3Rvcy1jbi1vLTAwMDQvODNjNDZmYWY1ODhlNDFjMzk4MmI3NTU3Y2Y1MDc2ODAvbWVkaWEtdmlkZW8tYXZjMS8\u002FYT0xNzY4JmNoPTAmY3I9MCZkcj0wJmVyPTAmbHI9ZGVmYXVsdCZjZD0wJTdDMCU3QzAlN0MwJmN2PTEmYnI9NTU4JmJ0PTU1OCZjcz0wJmRzPTEmbWltZV90eXBlPXZpZGVvX21wNCZxcz0wJnJjPVBEYzZPelZsTkRWcE9EZzNaVFUwUEVCcE16dHlhbVU2WmpvME56TXpORGN6TTBBd1h6WXhNelZnTmpVeExWNWdYakZlWVNOZk5UQXhjalJuY1dGZ0xTMWtMV0Z6Y3clM0QlM0QmbD0yMDIyMDYwMjA5MDc0ODAxMDE1MTA2MzE1NjA1N0ZGQTlG",
                "url_expire": 1654136698,
                "file_id": "a941a2cba405458f92c18b624897b439",
                "init_range": "0-953",
                "index_range": "954-3469",
                "check_info": "a:v0d004g10000c4d1t7jc77ub4g3o88b0|b:0-3469-cb1c07f15c9d9d5a44b718a4ec55fe79|c:0-3469-bd07",
                "fitter_info": null,
                "quality_type": 40,
                "language_id": 0
                 }, ...
          'normal' : {
           ...
           } ...
     ...
...
```
The url from `main_url` or `backup_url_*` seems encoded with base64. I have decode that and get the url but it return `HTTP error 403 Forbidden` in ffmpeg. 

The solution is to grab the url from the `normal` instead of `dash`. The url work in ffmpeg
@HobbyistDev Will you be making a PR?
I want to give it a try but i still don't know how to convert js to json. Maybe need to read `common.py` again
Use `js_to_json` from `utils`

```py
self._parse_json(..., transform_source=js_to_json)
```

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
@@ -720,6 +720,7 @@
     IwaraPlaylistIE,
     IwaraUserIE,
 )
+from .ixigua import IxiguaIE
 from .izlesene import IzleseneIE
 from .jable import (
     JableIE,
diff --git a/yt_dlp/extractor/ixigua.py b/yt_dlp/extractor/ixigua.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/ixigua.py
@@ -0,0 +1,84 @@
+import base64
+
+from .common import InfoExtractor
+from ..utils import (
+    ExtractorError,
+    get_element_by_id,
+    int_or_none,
+    js_to_json,
+    str_or_none,
+    traverse_obj,
+)
+
+
+class IxiguaIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:\w+\.)?ixigua\.com/(?:video/)?(?P<id>\d+).+'
+    _TESTS = [{
+        'url': 'https://www.ixigua.com/6996881461559165471',
+        'info_dict': {
+            'id': '6996881461559165471',
+            'ext': 'mp4',
+            'title': '盲目涉水风险大，亲身示范高水位行车注意事项',
+            'description': 'md5:8c82f46186299add4a1c455430740229',
+            'tags': ['video_car'],
+            'like_count': int,
+            'dislike_count': int,
+            'view_count': int,
+            'uploader': '懂车帝原创',
+            'uploader_id': '6480145787',
+            'thumbnail': r're:^https?://.+\.(avif|webp)',
+            'timestamp': 1629088414,
+            'duration': 1030,
+        }
+    }]
+
+    def _get_json_data(self, webpage, video_id):
+        js_data = get_element_by_id('SSR_HYDRATED_DATA', webpage)
+        if not js_data:
+            if self._cookies_passed:
+                raise ExtractorError('Failed to get SSR_HYDRATED_DATA')
+            raise ExtractorError('Cookies (not necessarily logged in) are needed', expected=True)
+
+        return self._parse_json(
+            js_data.replace('window._SSR_HYDRATED_DATA=', ''), video_id, transform_source=js_to_json)
+
+    def _media_selector(self, json_data):
+        for path, override in (
+            (('video_list', ), {}),
+            (('dynamic_video', 'dynamic_video_list'), {'acodec': 'none'}),
+            (('dynamic_video', 'dynamic_audio_list'), {'vcodec': 'none', 'ext': 'm4a'}),
+        ):
+            for media in traverse_obj(json_data, (..., *path, lambda _, v: v['main_url'])):
+                yield {
+                    'url': base64.b64decode(media['main_url']).decode(),
+                    'width': int_or_none(media.get('vwidth')),
+                    'height': int_or_none(media.get('vheight')),
+                    'fps': int_or_none(media.get('fps')),
+                    'vcodec': media.get('codec_type'),
+                    'format_id': str_or_none(media.get('quality_type')),
+                    'filesize': int_or_none(media.get('size')),
+                    'ext': 'mp4',
+                    **override,
+                }
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        webpage = self._download_webpage(url, video_id)
+        json_data = self._get_json_data(webpage, video_id)['anyVideo']['gidInformation']['packerData']['video']
+
+        formats = list(self._media_selector(json_data.get('videoResource')))
+        self._sort_formats(formats)
+        return {
+            'id': video_id,
+            'title': json_data.get('title'),
+            'description': json_data.get('video_abstract'),
+            'formats': formats,
+            'like_count': json_data.get('video_like_count'),
+            'duration': int_or_none(json_data.get('duration')),
+            'tags': [json_data.get('tag')],
+            'uploader_id': traverse_obj(json_data, ('user_info', 'user_id')),
+            'uploader': traverse_obj(json_data, ('user_info', 'name')),
+            'view_count': json_data.get('video_watch_count'),
+            'dislike_count': json_data.get('video_unlike_count'),
+            'timestamp': int_or_none(json_data.get('video_publish_time')),
+        }


Test patch: