From 933ed882e94ebfacc5e407dbd74fa25e672092c4 Mon Sep 17 00:00:00 2001 From: JC-Chung <52159296+JC-Chung@users.noreply.github.com> Date: Thu, 5 Jan 2023 19:23:34 +0800 Subject: [PATCH] [extractor/tiktok] Add `TikTokLive` extractor (#5637) Closes #3698 Authored by: JC-Chung --- yt_dlp/extractor/_extractors.py | 1 + yt_dlp/extractor/tiktok.py | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 53ec29364..7a390a8d2 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1890,6 +1890,7 @@ from .tiktok import ( TikTokEffectIE, TikTokTagIE, TikTokVMIE, + TikTokLiveIE, DouyinIE, ) from .tinypic import TinyPicIE diff --git a/yt_dlp/extractor/tiktok.py b/yt_dlp/extractor/tiktok.py index 709d944dc..cc96de364 100644 --- a/yt_dlp/extractor/tiktok.py +++ b/yt_dlp/extractor/tiktok.py @@ -11,6 +11,7 @@ from ..utils import ( HEADRequest, LazyList, UnsupportedError, + UserNotLive, get_element_by_id, get_first, int_or_none, @@ -980,3 +981,42 @@ class TikTokVMIE(InfoExtractor): 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[\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, + }