+
+
+class TwitchClipsIE(TwitchBaseIE):
+ IE_NAME = 'twitch:clips'
+ _VALID_URL = r'https?://(?:clips\.twitch\.tv/(?:[^/]+/)*|(?:www\.)?twitch\.tv/[^/]+/clip/)(?P<id>[^/?#&]+)'
+
+ _TESTS = [{
+ 'url': 'https://clips.twitch.tv/FaintLightGullWholeWheat',
+ 'md5': '761769e1eafce0ffebfb4089cb3847cd',
+ 'info_dict': {
+ 'id': '42850523',
+ 'ext': 'mp4',
+ 'title': 'EA Play 2016 Live from the Novo Theatre',
+ 'thumbnail': r're:^https?://.*\.jpg',
+ 'timestamp': 1465767393,
+ 'upload_date': '20160612',
+ 'creator': 'EA',
+ 'uploader': 'stereotype_',
+ 'uploader_id': '43566419',
+ },
+ }, {
+ # multiple formats
+ 'url': 'https://clips.twitch.tv/rflegendary/UninterestedBeeDAESuppy',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.twitch.tv/sergeynixon/clip/StormyThankfulSproutFutureMan',
+ 'only_matching': True,
+ }]
+
+ def _real_extract(self, url):
+ video_id = self._match_id(url)
+
+ status = self._download_json(
+ 'https://clips.twitch.tv/api/v2/clips/%s/status' % video_id,
+ video_id)
+
+ formats = []
+
+ for option in status['quality_options']:
+ if not isinstance(option, dict):
+ continue
+ source = url_or_none(option.get('source'))
+ if not source:
+ continue
+ formats.append({
+ 'url': source,
+ 'format_id': option.get('quality'),
+ 'height': int_or_none(option.get('quality')),
+ 'fps': int_or_none(option.get('frame_rate')),
+ })
+
+ self._sort_formats(formats)
+
+ info = {
+ 'formats': formats,
+ }
+
+ clip = self._call_api(
+ 'kraken/clips/%s' % video_id, video_id, fatal=False, headers={
+ 'Accept': 'application/vnd.twitchtv.v5+json',
+ })
+
+ if clip:
+ quality_key = qualities(('tiny', 'small', 'medium'))
+ thumbnails = []
+ thumbnails_dict = clip.get('thumbnails')
+ if isinstance(thumbnails_dict, dict):
+ for thumbnail_id, thumbnail_url in thumbnails_dict.items():
+ thumbnails.append({
+ 'id': thumbnail_id,
+ 'url': thumbnail_url,
+ 'preference': quality_key(thumbnail_id),
+ })
+
+ info.update({
+ 'id': clip.get('tracking_id') or video_id,
+ 'title': clip.get('title') or video_id,
+ 'duration': float_or_none(clip.get('duration')),
+ 'views': int_or_none(clip.get('views')),
+ 'timestamp': unified_timestamp(clip.get('created_at')),
+ 'thumbnails': thumbnails,
+ 'creator': try_get(clip, lambda x: x['broadcaster']['display_name'], compat_str),
+ 'uploader': try_get(clip, lambda x: x['curator']['display_name'], compat_str),
+ 'uploader_id': try_get(clip, lambda x: x['curator']['id'], compat_str),
+ })
+ else:
+ info.update({
+ 'title': video_id,
+ 'id': video_id,
+ })
+
+ return info