+ return self._parse_aweme_video_web(get_first(render_data, ('aweme', 'detail')), url)
+
+
+class TikTokVMIE(InfoExtractor):
+ _VALID_URL = r'https?://(?:(?:vm|vt)\.tiktok\.com|(?:www\.)tiktok\.com/t)/(?P<id>\w+)'
+ IE_NAME = 'vm.tiktok'
+
+ _TESTS = [{
+ 'url': 'https://www.tiktok.com/t/ZTRC5xgJp',
+ 'info_dict': {
+ 'id': '7170520270497680683',
+ 'ext': 'mp4',
+ 'title': 'md5:c64f6152330c2efe98093ccc8597871c',
+ 'uploader_id': '6687535061741700102',
+ 'upload_date': '20221127',
+ 'view_count': int,
+ 'like_count': int,
+ 'comment_count': int,
+ 'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAObqu3WCTXxmw2xwZ3iLEHnEecEIw7ks6rxWqOqOhaPja9BI7gqUQnjw8_5FSoDXX',
+ 'album': 'Wave of Mutilation: Best of Pixies',
+ 'thumbnail': r're:https://.+\.webp.*',
+ 'duration': 5,
+ 'timestamp': 1669516858,
+ 'repost_count': int,
+ 'artist': 'Pixies',
+ 'track': 'Where Is My Mind?',
+ 'description': 'md5:c64f6152330c2efe98093ccc8597871c',
+ 'uploader': 'sigmachaddeus',
+ 'creator': 'SigmaChad',
+ },
+ }, {
+ 'url': 'https://vm.tiktok.com/ZTR45GpSF/',
+ 'info_dict': {
+ 'id': '7106798200794926362',
+ 'ext': 'mp4',
+ 'title': 'md5:edc3e7ea587847f8537468f2fe51d074',
+ 'uploader_id': '6997695878846268418',
+ 'upload_date': '20220608',
+ 'view_count': int,
+ 'like_count': int,
+ 'comment_count': int,
+ 'thumbnail': r're:https://.+\.webp.*',
+ 'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAdZ_NcPPgMneaGrW0hN8O_J_bwLshwNNERRF5DxOw2HKIzk0kdlLrR8RkVl1ksrMO',
+ 'duration': 29,
+ 'timestamp': 1654680400,
+ 'repost_count': int,
+ 'artist': 'Akihitoko',
+ 'track': 'original sound',
+ 'description': 'md5:edc3e7ea587847f8537468f2fe51d074',
+ 'uploader': 'akihitoko1',
+ 'creator': 'Akihitoko',
+ },
+ }, {
+ 'url': 'https://vt.tiktok.com/ZSe4FqkKd',
+ 'only_matching': True,
+ }]
+
+ def _real_extract(self, url):
+ new_url = self._request_webpage(
+ HEADRequest(url), self._match_id(url), headers={'User-Agent': 'facebookexternalhit/1.1'}).geturl()
+ if self.suitable(new_url): # Prevent infinite loop in case redirect fails
+ raise UnsupportedError(new_url)
+ return self.url_result(new_url)
+
+
+class TikTokLiveIE(InfoExtractor):
+ _VALID_URL = r'https?://(?:www\.)?tiktok\.com/@(?P<id>[\w\.-]+)/live'
+ IE_NAME = 'tiktok:live'
+
+ _TESTS = [{
+ 'url': 'https://www.tiktok.com/@iris04201/live',
+ 'only_matching': True,
+ }]
+
+ def _real_extract(self, url):
+ uploader = self._match_id(url)
+ webpage = self._download_webpage(url, uploader, headers={'User-Agent': 'User-Agent:Mozilla/5.0'})
+ room_id = self._html_search_regex(r'snssdk\d*://live\?room_id=(\d+)', webpage, 'room ID', default=None)
+ if not room_id:
+ raise UserNotLive(video_id=uploader)
+ live_info = traverse_obj(self._download_json(
+ 'https://www.tiktok.com/api/live/detail/', room_id, query={
+ 'aid': '1988',
+ 'roomID': room_id,
+ }), 'LiveRoomInfo', expected_type=dict, default={})
+
+ if 'status' not in live_info:
+ raise ExtractorError('Unexpected response from TikTok API')
+ # status = 2 if live else 4
+ if not int_or_none(live_info['status']) == 2:
+ raise UserNotLive(video_id=uploader)
+
+ return {
+ 'id': room_id,
+ 'title': live_info.get('title') or self._html_search_meta(['og:title', 'twitter:title'], webpage, default=''),
+ 'uploader': uploader,
+ 'uploader_id': traverse_obj(live_info, ('ownerInfo', 'id')),
+ 'creator': traverse_obj(live_info, ('ownerInfo', 'nickname')),
+ 'concurrent_view_count': traverse_obj(live_info, ('liveRoomStats', 'userCount'), expected_type=int),
+ 'formats': self._extract_m3u8_formats(live_info['liveUrl'], room_id, 'mp4', live=True),
+ 'is_live': True,
+ }