From ce18a19be9af1e227e7162636cbf6b277adc1b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergey=20M=E2=80=A4?= Date: Sun, 2 Dec 2018 02:39:22 +0700 Subject: [PATCH] [tiktok] Improve extraction and add support for user pages (closes #18135) --- youtube_dl/extractor/extractors.py | 5 +- youtube_dl/extractor/tiktok.py | 126 +++++++++++++++++++---------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py index 54733107833..0baed6b2742 100644 --- a/youtube_dl/extractor/extractors.py +++ b/youtube_dl/extractor/extractors.py @@ -1124,7 +1124,10 @@ from .thisav import ThisAVIE from .thisoldhouse import ThisOldHouseIE from .threeqsdn import ThreeQSDNIE -from .tiktok import TikTokIE +from .tiktok import ( + TikTokIE, + TikTokUserIE, +) from .tinypic import TinyPicIE from .tmz import ( TMZIE, diff --git a/youtube_dl/extractor/tiktok.py b/youtube_dl/extractor/tiktok.py index d71b09c663f..083e9f36d80 100644 --- a/youtube_dl/extractor/tiktok.py +++ b/youtube_dl/extractor/tiktok.py @@ -4,6 +4,7 @@ from .common import InfoExtractor from ..utils import ( compat_str, + ExtractorError, int_or_none, str_or_none, try_get, @@ -11,69 +12,106 @@ ) -class TikTokIE(InfoExtractor): - _VALID_URL = r'https?://(?:m\.)?tiktok\.com/v/(?P[0-9]+)' - _TEST = { - 'url': 'https://m.tiktok.com/v/6606727368545406213.html', - 'md5': 'd584b572e92fcd48888051f238022420', - 'info_dict': { - 'id': '6606727368545406213', - 'ext': 'mp4', - 'title': 'Zureeal on TikTok', - 'thumbnail': r're:^https?://.*~noop.image', - 'description': '#bowsette#mario#cosplay#uk#lgbt#gaming#asian#bowsettecosplay', - 'uploader': 'Zureeal', - 'width': 540, - 'height': 960, - } - } - - def _real_extract(self, url): - video_id = self._match_id(url) - webpage = self._download_webpage(url, video_id) - - data = self._parse_json( - self._search_regex( - r'var\s+data\s*=\s*({.+?});', webpage, 'data' - ), video_id) - - title = self._og_search_title(webpage) - +class TikTokBaseIE(InfoExtractor): + def _extract_aweme(self, data): + video = data['video'] description = str_or_none(try_get(data, lambda x: x['desc'])) - width = int_or_none(try_get(data, lambda x: x['video']['width'])) - height = int_or_none(try_get(data, lambda x: x['video']['height'])) + width = int_or_none(try_get(data, lambda x: video['width'])) + height = int_or_none(try_get(data, lambda x: video['height'])) + format_urls = set() formats = [] - - for count, (key, label) in enumerate((('play_addr_lowbr', 'Low'), ('play_addr', 'Normal'), ('download_addr', 'Download')), -2): - for format in try_get(data, lambda x: x['video'][key]['url_list']): + for format_id in ( + 'play_addr_lowbr', 'play_addr', 'play_addr_h264', + 'download_addr'): + for format in try_get( + video, lambda x: x[format_id]['url_list'], list) or []: format_url = url_or_none(format) if not format_url: continue + if format_url in format_urls: + continue + format_urls.add(format_url) formats.append({ 'url': format_url, 'ext': 'mp4', 'height': height, 'width': width, - 'format_note': label, - 'quality': count }) - self._sort_formats(formats) + thumbnail = url_or_none(try_get( + video, lambda x: x['cover']['url_list'][0], compat_str)) uploader = try_get(data, lambda x: x['author']['nickname'], compat_str) + timestamp = int_or_none(data.get('create_time')) + comment_count = int_or_none(data.get('comment_count')) or int_or_none( + try_get(data, lambda x: x['statistics']['comment_count'])) + repost_count = int_or_none(try_get( + data, lambda x: x['statistics']['share_count'])) - thumbnail = url_or_none( - try_get( - data, lambda x: x['video']['cover']['url_list'][0], compat_str)) + aweme_id = data['aweme_id'] return { - 'id': video_id, - 'title': title, + 'id': aweme_id, + 'title': uploader or aweme_id, 'description': description, + 'thumbnail': thumbnail, 'uploader': uploader, + 'timestamp': timestamp, + 'comment_count': comment_count, + 'repost_count': repost_count, 'formats': formats, - 'thumbnail': thumbnail, - 'width': width, - 'height': height, } + + +class TikTokIE(TikTokBaseIE): + _VALID_URL = r'https?://(?:m\.)?tiktok\.com/v/(?P\d+)' + _TEST = { + 'url': 'https://m.tiktok.com/v/6606727368545406213.html', + 'md5': 'd584b572e92fcd48888051f238022420', + 'info_dict': { + 'id': '6606727368545406213', + 'ext': 'mp4', + 'title': 'Zureeal', + 'description': '#bowsette#mario#cosplay#uk#lgbt#gaming#asian#bowsettecosplay', + 'thumbnail': r're:^https?://.*~noop.image', + 'uploader': 'Zureeal', + 'timestamp': 1538248586, + 'upload_date': '20180929', + 'comment_count': int, + 'repost_count': int, + } + } + + def _real_extract(self, url): + video_id = self._match_id(url) + webpage = self._download_webpage(url, video_id) + data = self._parse_json(self._search_regex( + r'\bdata\s*=\s*({.+?})\s*;', webpage, 'data'), video_id) + return self._extract_aweme(data) + + +class TikTokUserIE(TikTokBaseIE): + _VALID_URL = r'https?://(?:m\.)?tiktok\.com/h5/share/usr/(?P\d+)' + _TEST = { + 'url': 'https://m.tiktok.com/h5/share/usr/188294915489964032.html', + 'info_dict': { + 'id': '188294915489964032', + }, + 'playlist_mincount': 24, + } + + def _real_extract(self, url): + user_id = self._match_id(url) + data = self._download_json( + 'https://m.tiktok.com/h5/share/usr/list/%s/' % user_id, user_id, + query={'_signature': '_'}) + entries = [] + for aweme in data['aweme_list']: + try: + entry = self._extract_aweme(aweme) + except ExtractorError: + continue + entry['extractor_key'] = TikTokIE.ie_key() + entries.append(entry) + return self.playlist_result(entries, user_id)