Repository: yt-dlp/yt-dlp

Issue:
Support for fptplay.vn
### Checklist

- [X] I'm reporting a site feature 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 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)
- [ ] 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

### Region

Vietnam

### Example URLs

https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc

### Description

Videos are only accessible in Vietnam. Yt-dlp download URL is https://vod02-cdn.fptplay.net/ovod/_definst_/smil:mp4/encoded/20220117/anime_paytv_aniplus_aotss4_2022_ep01/d729a85e8520db6b441a6980742a4898.smil/playlist.m3u8?token=eyJoYXNoX3ZhbHVlIjogIjk5MjA2MjExMDQwYmM4MzAwNDBkNjg5YTk2MjFjMjY1IiwgInZpZGVvX2lkIjogIjYxY2VhOTYwNzg1MDdiYzUxMDI1MjdmYyIsICJzZXJ2ZXJfdGltZSI6IDE2NDU1NTY2MzAsICJ2YWxpZF9taW51dGVzIjogNzIwfQ (no proxies needed if outside VIetnam)

### Verbose log

```shell
yt-dlp -v "https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc"
[debug] Command-line config: ['-v', 'https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, err utf-8, pref UTF-8
[debug] yt-dlp version 2022.02.04 [c1653e9ef]
[debug] Python version 3.9.0 (CPython 64bit) - macOS-12.0.1-x86_64-i386-64bit
[debug] exe versions: ffmpeg 4.3.1, ffprobe 4.3.1, rtmpdump 2.4
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
[debug] [generic] Extracting URL: https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc
[generic] dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc: Downloading webpage
[generic] dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1381, in wrapper
    return func(self, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1451, in __extract_info
    ie_result = ie.extract(url)
  File "/usr/local/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 612, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/lib/python3.9/site-packages/yt_dlp/extractor/generic.py", line 3986, in _real_extract
    raise UnsupportedError(url)
yt_dlp.utils.UnsupportedError: Unsupported URL: https://fptplay.vn/xem-video/dai-chien-nguoi-khong-lo-phan-cuoi-attack-on-titan-final-season-61cea96078507bc5102527fc
```



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
@@ -520,6 +520,7 @@
     FoxNewsArticleIE,
 )
 from .foxsports import FoxSportsIE
+from .fptplay import FptplayIE
 from .franceculture import FranceCultureIE
 from .franceinter import FranceInterIE
 from .francetv import (
diff --git a/yt_dlp/extractor/fptplay.py b/yt_dlp/extractor/fptplay.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/fptplay.py
@@ -0,0 +1,102 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import hashlib
+import time
+import urllib.parse
+
+from .common import InfoExtractor
+from ..utils import (
+    join_nonempty,
+)
+
+
+class FptplayIE(InfoExtractor):
+    _VALID_URL = r'https?://fptplay\.vn/(?P<type>xem-video)/[^/]+\-(?P<id>\w+)(?:/tap-(?P<episode>[^/]+)?/?(?:[?#]|$)|)'
+    _GEO_COUNTRIES = ['VN']
+    IE_NAME = 'fptplay'
+    IE_DESC = 'fptplay.vn'
+    _TESTS = [{
+        'url': 'https://fptplay.vn/xem-video/nhan-duyen-dai-nhan-xin-dung-buoc-621a123016f369ebbde55945',
+        'md5': 'ca0ee9bc63446c0c3e9a90186f7d6b33',
+        'info_dict': {
+            'id': '621a123016f369ebbde55945',
+            'ext': 'mp4',
+            'title': 'Nhân Duyên Đại Nhân Xin Dừng Bước - Ms. Cupid In Love',
+            'description': 'md5:23cf7d1ce0ade8e21e76ae482e6a8c6c',
+        },
+    }, {
+        'url': 'https://fptplay.vn/xem-video/ma-toi-la-dai-gia-61f3aa8a6b3b1d2e73c60eb5/tap-3',
+        'md5': 'b35be968c909b3e4e1e20ca45dd261b1',
+        'info_dict': {
+            'id': '61f3aa8a6b3b1d2e73c60eb5',
+            'ext': 'mp4',
+            'title': 'Má Tôi Là Đại Gia - 3',
+            'description': 'md5:ff8ba62fb6e98ef8875c42edff641d1c',
+        },
+    }, {
+        'url': 'https://fptplay.vn/xem-video/nha-co-chuyen-hi-alls-well-ends-well-1997-6218995f6af792ee370459f0',
+        'only_matching': True,
+    }]
+
+    def _real_extract(self, url):
+        type_url, video_id, episode = self._match_valid_url(url).group('type', 'id', 'episode')
+        webpage = self._download_webpage(url, video_id=video_id, fatal=False)
+        info = self._download_json(self.get_api_with_st_token(video_id, episode or 0), video_id)
+        formats, subtitles = self._extract_m3u8_formats_and_subtitles(info['data']['url'], video_id, 'mp4')
+        self._sort_formats(formats)
+        return {
+            'id': video_id,
+            'title': join_nonempty(
+                self._html_search_meta(('og:title', 'twitter:title'), webpage), episode, delim=' - '),
+            'description': self._html_search_meta(['og:description', 'twitter:description'], webpage),
+            'formats': formats,
+            'subtitles': subtitles,
+        }
+
+    def get_api_with_st_token(self, video_id, episode):
+        path = f'/api/v6.2_w/stream/vod/{video_id}/{episode}/auto_vip'
+        timestamp = int(time.time()) + 10800
+
+        t = hashlib.md5(f'WEBv6Dkdsad90dasdjlALDDDS{timestamp}{path}'.encode()).hexdigest().upper()
+        r = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+        n = [int(f'0x{t[2 * o: 2 * o + 2]}', 16) for o in range(len(t) // 2)]
+
+        def convert(e):
+            t = ''
+            n = 0
+            i = [0, 0, 0]
+            a = [0, 0, 0, 0]
+            s = len(e)
+            c = 0
+            for z in range(s, 0, -1):
+                if n <= 3:
+                    i[n] = e[c]
+                n += 1
+                c += 1
+                if 3 == n:
+                    a[0] = (252 & i[0]) >> 2
+                    a[1] = ((3 & i[0]) << 4) + ((240 & i[1]) >> 4)
+                    a[2] = ((15 & i[1]) << 2) + ((192 & i[2]) >> 6)
+                    a[3] = (63 & i[2])
+                    for v in range(4):
+                        t += r[a[v]]
+                    n = 0
+            if n:
+                for o in range(n, 3):
+                    i[o] = 0
+
+                for o in range(n + 1):
+                    a[0] = (252 & i[0]) >> 2
+                    a[1] = ((3 & i[0]) << 4) + ((240 & i[1]) >> 4)
+                    a[2] = ((15 & i[1]) << 2) + ((192 & i[2]) >> 6)
+                    a[3] = (63 & i[2])
+                    t += r[a[o]]
+                n += 1
+                while n < 3:
+                    t += ''
+                    n += 1
+            return t
+
+        st_token = convert(n).replace('+', '-').replace('/', '_').replace('=', '')
+        return f'https://api.fptplay.net{path}?{urllib.parse.urlencode({"st": st_token, "e": timestamp})}'


Test patch: