2 from __future__ 
import unicode_literals
 
   6 from .common 
import InfoExtractor
 
  19 class RoosterTeethIE(InfoExtractor
): 
  20     _VALID_URL 
= r
'https?://(?:.+?\.)?roosterteeth\.com/(?:episode|watch)/(?P<id>[^/?#&]+)' 
  21     _LOGIN_URL 
= 'https://roosterteeth.com/login' 
  22     _NETRC_MACHINE 
= 'roosterteeth' 
  24         'url': 'http://roosterteeth.com/episode/million-dollars-but-season-2-million-dollars-but-the-game-announcement', 
  25         'md5': 'e2bd7764732d785ef797700a2489f212', 
  28             'display_id': 'million-dollars-but-season-2-million-dollars-but-the-game-announcement', 
  30             'title': 'Million Dollars, But... The Game Announcement', 
  31             'description': 'md5:168a54b40e228e79f4ddb141e89fe4f5', 
  32             'thumbnail': r
're:^https?://.*\.png$', 
  33             'series': 'Million Dollars, But...', 
  34             'episode': 'Million Dollars, But... The Game Announcement', 
  37         'url': 'http://achievementhunter.roosterteeth.com/episode/off-topic-the-achievement-hunter-podcast-2016-i-didn-t-think-it-would-pass-31', 
  38         'only_matching': True, 
  40         'url': 'http://funhaus.roosterteeth.com/episode/funhaus-shorts-2016-austin-sucks-funhaus-shorts', 
  41         'only_matching': True, 
  43         'url': 'http://screwattack.roosterteeth.com/episode/death-battle-season-3-mewtwo-vs-shadow', 
  44         'only_matching': True, 
  46         'url': 'http://theknow.roosterteeth.com/episode/the-know-game-news-season-1-boring-steam-sales-are-better', 
  47         'only_matching': True, 
  49         # only available for FIRST members 
  50         'url': 'http://roosterteeth.com/episode/rt-docs-the-world-s-greatest-head-massage-the-world-s-greatest-head-massage-an-asmr-journey-part-one', 
  51         'only_matching': True, 
  53         'url': 'https://roosterteeth.com/watch/million-dollars-but-season-2-million-dollars-but-the-game-announcement', 
  54         'only_matching': True, 
  58         username
, password 
= self
._get
_login
_info
() 
  62         login_page 
= self
._download
_webpage
( 
  63             self
._LOGIN
_URL
, None, 
  64             note
='Downloading login page', 
  65             errnote
='Unable to download login page') 
  67         login_form 
= self
._hidden
_inputs
(login_page
) 
  74         login_request 
= self
._download
_webpage
( 
  75             self
._LOGIN
_URL
, None, 
  77             data
=urlencode_postdata(login_form
), 
  79                 'Referer': self
._LOGIN
_URL
, 
  82         if not any(re
.search(p
, login_request
) for p 
in ( 
  83                 r
'href=["\']https?
://(?
:www\
.)?roosterteeth\
.com
/logout
"', 
  85             error = self._html_search_regex( 
  86                 r'(?s)<div[^>]+class=(["\']).*?
\balert
-danger
\b.*?\
1[^
>]*>(?
:\s
*<button
[^
>]*>.*?
</button
>)?
(?P
<error
>.+?
)</div
>', 
  87                 login_request, 'alert
', default=None, group='error
') 
  89                 raise ExtractorError('Unable to login
: %s' % error, expected=True) 
  90             raise ExtractorError('Unable to log 
in') 
  92     def _real_initialize(self): 
  95     def _real_extract(self, url): 
  96         display_id = self._match_id(url) 
  97         api_episode_url = 'https
://svod
-be
.roosterteeth
.com
/api
/v1
/episodes
/%s' % display_id 
 100             m3u8_url = self._download_json( 
 101                 api_episode_url + '/videos
', display_id, 
 102                 'Downloading video JSON metadata
')['data
'][0]['attributes
']['url
'] 
 103         except ExtractorError as e: 
 104             if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: 
 105                 if self._parse_json(e.cause.read().decode(), display_id).get('access
') is False: 
 106                     self.raise_login_required( 
 107                         '%s is only available 
for FIRST members
' % display_id) 
 110         formats = self._extract_m3u8_formats( 
 111             m3u8_url, display_id, 'mp4
', 'm3u8_native
', m3u8_id='hls
') 
 112         self._sort_formats(formats) 
 114         episode = self._download_json( 
 115             api_episode_url, display_id, 
 116             'Downloading episode JSON metadata
')['data
'][0] 
 117         attributes = episode['attributes
'] 
 118         title = attributes.get('title
') or attributes['display_title
'] 
 119         video_id = compat_str(episode['id']) 
 122         for image in episode.get('included
', {}).get('images
', []): 
 123             if image.get('type') == 'episode_image
': 
 124                 img_attributes = image.get('attributes
') or {} 
 125                 for k in ('thumb
', 'small
', 'medium
', 'large
'): 
 126                     img_url = img_attributes.get(k) 
 135             'display_id
': display_id, 
 137             'description
': attributes.get('description
') or attributes.get('caption
'), 
 138             'thumbnails
': thumbnails, 
 139             'series
': attributes.get('show_title
'), 
 140             'season_number
': int_or_none(attributes.get('season_number
')), 
 141             'season_id
': attributes.get('season_id
'), 
 143             'episode_number
': int_or_none(attributes.get('number
')), 
 144             'episode_id
': str_or_none(episode.get('uuid
')), 
 146             'channel_id
': attributes.get('channel_id
'), 
 147             'duration
': int_or_none(attributes.get('length
')),