X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=youtube_dl%2Fextractor%2Ftwitch.py;h=340cadcf5f2a00f855d4cbb47c1b46ba09ec502a;hb=0920e5830f890580ec16cdd10bfe8def73a1a09f;hp=f70002f68495e3e910586fc7ddeccc74523ab665;hpb=d63528c8c7965545969e77b983e0e42d2e41e31f;p=youtube-dl diff --git a/youtube_dl/extractor/twitch.py b/youtube_dl/extractor/twitch.py index f70002f68..340cadcf5 100644 --- a/youtube_dl/extractor/twitch.py +++ b/youtube_dl/extractor/twitch.py @@ -3,9 +3,11 @@ from __future__ import unicode_literals import itertools import re +import random from .common import InfoExtractor from ..compat import ( + compat_str, compat_urllib_parse, compat_urllib_request, ) @@ -16,9 +18,10 @@ from ..utils import ( class TwitchBaseIE(InfoExtractor): - _VALID_URL_BASE = r'http://(?:www\.)?twitch\.tv' + _VALID_URL_BASE = r'https?://(?:www\.)?twitch\.tv' _API_BASE = 'https://api.twitch.tv' + _USHER_BASE = 'http://usher.twitch.tv' _LOGIN_URL = 'https://secure.twitch.tv/user/login' def _handle_error(self, response): @@ -148,14 +151,17 @@ class TwitchChapterIE(TwitchItemBaseIE): _ITEM_TYPE = 'chapter' _ITEM_SHORTCUT = 'c' - _TEST = { + _TESTS = [{ 'url': 'http://www.twitch.tv/acracingleague/c/5285812', 'info_dict': { 'id': 'c5285812', 'title': 'ACRL Off Season - Sports Cars @ Nordschleife', }, 'playlist_mincount': 3, - } + }, { + 'url': 'http://www.twitch.tv/tsm_theoddone/c/2349361', + 'only_matching': True, + }] class TwitchVodIE(TwitchItemBaseIE): @@ -191,8 +197,8 @@ class TwitchVodIE(TwitchItemBaseIE): '%s/api/vods/%s/access_token' % (self._API_BASE, item_id), item_id, 'Downloading %s access token' % self._ITEM_TYPE) formats = self._extract_m3u8_formats( - 'http://usher.twitch.tv/vod/%s?nauth=%s&nauthsig=%s' - % (item_id, access_token['token'], access_token['sig']), + '%s/vod/%s?nauth=%s&nauthsig=%s' + % (self._USHER_BASE, item_id, access_token['token'], access_token['sig']), item_id, 'mp4') info['formats'] = formats return info @@ -241,7 +247,7 @@ class TwitchProfileIE(TwitchPlaylistBaseIE): class TwitchPastBroadcastsIE(TwitchPlaylistBaseIE): - IE_NAME = 'twitch:profile' + IE_NAME = 'twitch:past_broadcasts' _VALID_URL = r'%s/(?P[^/]+)/profile/past_broadcasts/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE _PLAYLIST_URL = TwitchPlaylistBaseIE._PLAYLIST_URL + '&broadcasts=true' _PLAYLIST_TYPE = 'past broadcasts' @@ -254,3 +260,92 @@ class TwitchPastBroadcastsIE(TwitchPlaylistBaseIE): }, 'playlist_mincount': 54, } + + +class TwitchStreamIE(TwitchBaseIE): + IE_NAME = 'twitch:stream' + _VALID_URL = r'%s/(?P[^/]+)/?(?:\#.*)?$' % TwitchBaseIE._VALID_URL_BASE + + _TEST = { + 'url': 'http://www.twitch.tv/shroomztv', + 'info_dict': { + 'id': '12772022048', + 'display_id': 'shroomztv', + 'ext': 'mp4', + 'title': 're:^ShroomzTV [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$', + 'description': 'H1Z1 - lonewolfing with ShroomzTV | A3 Battle Royale later - @ShroomzTV', + 'is_live': True, + 'timestamp': 1421928037, + 'upload_date': '20150122', + 'uploader': 'ShroomzTV', + 'uploader_id': 'shroomztv', + 'view_count': int, + }, + 'params': { + # m3u8 download + 'skip_download': True, + }, + } + + def _real_extract(self, url): + channel_id = self._match_id(url) + + stream = self._download_json( + '%s/kraken/streams/%s' % (self._API_BASE, channel_id), channel_id, + 'Downloading stream JSON').get('stream') + + # Fallback on profile extraction if stream is offline + if not stream: + return self.url_result( + 'http://www.twitch.tv/%s/profile' % channel_id, + 'TwitchProfile', channel_id) + + access_token = self._download_json( + '%s/api/channels/%s/access_token' % (self._API_BASE, channel_id), channel_id, + 'Downloading channel access token') + + query = { + 'allow_source': 'true', + 'p': random.randint(1000000, 10000000), + 'player': 'twitchweb', + 'segment_preference': '4', + 'sig': access_token['sig'], + 'token': access_token['token'], + } + + formats = self._extract_m3u8_formats( + '%s/api/channel/hls/%s.m3u8?%s' + % (self._USHER_BASE, channel_id, compat_urllib_parse.urlencode(query).encode('utf-8')), + channel_id, 'mp4') + + view_count = stream.get('viewers') + timestamp = parse_iso8601(stream.get('created_at')) + + channel = stream['channel'] + title = self._live_title(channel.get('display_name') or channel.get('name')) + description = channel.get('status') + + thumbnails = [] + for thumbnail_key, thumbnail_url in stream['preview'].items(): + m = re.search(r'(?P\d+)x(?P\d+)\.jpg$', thumbnail_key) + if not m: + continue + thumbnails.append({ + 'url': thumbnail_url, + 'width': int(m.group('width')), + 'height': int(m.group('height')), + }) + + return { + 'id': compat_str(stream['_id']), + 'display_id': channel_id, + 'title': title, + 'description': description, + 'thumbnails': thumbnails, + 'uploader': channel.get('display_name'), + 'uploader_id': channel.get('name'), + 'timestamp': timestamp, + 'view_count': view_count, + 'formats': formats, + 'is_live': True, + }