1 from __future__ 
import unicode_literals
 
   6 from .common 
import InfoExtractor
 
  10     compat_urllib_request
, 
  20 class PluralsightIE(InfoExtractor
): 
  21     IE_NAME 
= 'pluralsight' 
  22     _VALID_URL 
= r
'https?://(?:www\.)?pluralsight\.com/training/player\?author=(?P<author>[^&]+)&name=(?P<name>[^&]+)(?:&mode=live)?&clip=(?P<clip>\d+)&course=(?P<course>[^&]+)' 
  23     _LOGIN_URL 
= 'https://www.pluralsight.com/id/' 
  24     _NETRC_MACHINE 
= 'pluralsight' 
  27         'url': 'http://www.pluralsight.com/training/player?author=mike-mckeown&name=hosting-sql-server-windows-azure-iaas-m7-mgmt&mode=live&clip=3&course=hosting-sql-server-windows-azure-iaas', 
  28         'md5': '4d458cf5cf4c593788672419a8dd4cf8', 
  30             'id': 'hosting-sql-server-windows-azure-iaas-m7-mgmt-04', 
  32             'title': 'Management of SQL Server - Demo Monitoring', 
  35         'skip': 'Requires pluralsight account credentials', 
  38     def _real_initialize(self
): 
  42         (username
, password
) = self
._get
_login
_info
() 
  44             self
.raise_login_required('Pluralsight account is required') 
  46         login_page 
= self
._download
_webpage
( 
  47             self
._LOGIN
_URL
, None, 'Downloading login page') 
  49         login_form 
= self
._hidden
_inputs
(login_page
) 
  52             'Username': username
.encode('utf-8'), 
  53             'Password': password
.encode('utf-8'), 
  56         post_url 
= self
._search
_regex
( 
  57             r
'<form[^>]+action=(["\'])(?P
<url
>.+?
)\
1', login_page, 
  58             'post url
', default=self._LOGIN_URL, group='url
') 
  60         if not post_url.startswith('http
'): 
  61             post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url) 
  63         request = compat_urllib_request.Request( 
  64             post_url, compat_urllib_parse.urlencode(login_form).encode('utf
-8')) 
  65         request.add_header('Content
-Type
', 'application
/x
-www
-form
-urlencoded
') 
  67         response = self._download_webpage( 
  68             request, None, 'Logging 
in as %s' % username) 
  70         error = self._search_regex( 
  71             r'<span
[^
>]+class="field-validation-error"[^
>]*>([^
<]+)</span
>', 
  72             response, 'error message
', default=None) 
  74             raise ExtractorError('Unable to login
: %s' % error, expected=True) 
  76     def _real_extract(self, url): 
  77         mobj = re.match(self._VALID_URL, url) 
  78         author = mobj.group('author
') 
  79         name = mobj.group('name
') 
  80         clip_id = mobj.group('clip
') 
  81         course = mobj.group('course
') 
  83         display_id = '%s-%s' % (name, clip_id) 
  85         webpage = self._download_webpage(url, display_id) 
  87         collection = self._parse_json( 
  89                 r'moduleCollection\s
*:\s
*new\s
+ModuleCollection\
((\
[.+?\
])\s
*,\s
*\$rootScope\
)', 
  93         module, clip = None, None 
  95         for module_ in collection: 
  96             if module_.get('moduleName
') == name: 
  98                 for clip_ in module_.get('clips
', []): 
  99                     clip_index = clip_.get('clipIndex
') 
 100                     if clip_index is None: 
 102                     if compat_str(clip_index) == clip_id: 
 107             raise ExtractorError('Unable to resolve clip
') 
 110             'low
': {'width
': 640, 'height
': 480}, 
 111             'medium
': {'width
': 848, 'height
': 640}, 
 112             'high
': {'width
': 1024, 'height
': 768}, 
 115         ALLOWED_QUALITIES = ( 
 117             ('mp4
', ('low
', 'medium
', 'high
',)), 
 121         for ext, qualities in ALLOWED_QUALITIES: 
 122             for quality in qualities: 
 123                 f = QUALITIES[quality].copy() 
 132                     'q
': '%dx%d' % (f['width
'], f['height
']), 
 134                 request = compat_urllib_request.Request( 
 135                     'http
://www
.pluralsight
.com
/training
/Player
/ViewClip
', 
 136                     json.dumps(clip_post).encode('utf
-8')) 
 137                 request.add_header('Content
-Type
', 'application
/json
;charset
=utf
-8') 
 138                 format_id = '%s-%s' % (ext, quality) 
 139                 clip_url = self._download_webpage( 
 140                     request, display_id, 'Downloading 
%s URL
' % format_id, fatal=False) 
 146                     'format_id
': format_id, 
 149         self._sort_formats(formats) 
 152         # http://www.pluralsight.com/training/Player/ViewClip + cap = true 
 154         # http://www.pluralsight.com/training/Player/Captions 
 155         # { a = author, cn = clip_id, lc = end, m = name } 
 158             'id': clip['clipName
'], 
 159             'title
': '%s - %s' % (module['title
'], clip['title
']), 
 160             'duration
': int_or_none(clip.get('duration
')) or parse_duration(clip.get('formattedDuration
')), 
 166 class PluralsightCourseIE(InfoExtractor): 
 167     IE_NAME = 'pluralsight
:course
' 
 168     _VALID_URL = r'https?
://(?
:www\
.)?pluralsight\
.com
/courses
/(?P
<id>[^
/]+)' 
 170         # Free course from Pluralsight Starter Subscription for Microsoft TechNet 
 171         # https://offers.pluralsight.com/technet?loc=zTS3z&prod=zOTprodz&tech=zOttechz&prog=zOTprogz&type=zSOz&media=zOTmediaz&country=zUSz 
 172         'url
': 'http
://www
.pluralsight
.com
/courses
/hosting
-sql
-server
-windows
-azure
-iaas
', 
 174             'id': 'hosting
-sql
-server
-windows
-azure
-iaas
', 
 175             'title
': 'Hosting SQL Server 
in Microsoft Azure IaaS Fundamentals
', 
 176             'description
': 'md5
:61b37e60f21c4b2f91dc621a977d0986
', 
 178         'playlist_count
': 31, 
 181     def _real_extract(self, url): 
 182         course_id = self._match_id(url) 
 186         course = self._download_json( 
 187             'http
://www
.pluralsight
.com
/data
/course
/%s' % course_id, 
 188             course_id, 'Downloading course JSON
') 
 190         title = course['title
'] 
 191         description = course.get('description
') or course.get('shortDescription
') 
 193         course_data = self._download_json( 
 194             'http
://www
.pluralsight
.com
/data
/course
/content
/%s' % course_id, 
 195             course_id, 'Downloading course data JSON
') 
 198         for module in course_data: 
 199             for clip in module.get('clips
', []): 
 200                 player_parameters = clip.get('playerParameters
') 
 201                 if not player_parameters: 
 203                 entries.append(self.url_result( 
 204                     'http
://www
.pluralsight
.com
/training
/player?
%s' % player_parameters, 
 207         return self.playlist_result(entries, course_id, title, description)