2016-11-12 12:52:02 -08:00
# coding: utf-8
from __future__ import unicode_literals
import re
import json
from . common import InfoExtractor
2017-12-29 16:28:18 -08:00
from . . compat import (
compat_str ,
compat_urlparse ,
)
2016-11-12 12:52:02 -08:00
from . . utils import (
ExtractorError ,
float_or_none ,
2017-12-29 16:28:18 -08:00
mimetype2ext ,
2019-03-30 11:17:30 -07:00
str_or_none ,
try_get ,
2017-12-29 16:28:18 -08:00
unescapeHTML ,
unsmuggle_url ,
2018-07-21 05:08:28 -07:00
url_or_none ,
2017-12-29 16:28:18 -08:00
urljoin ,
2016-11-12 12:52:02 -08:00
)
2019-03-30 11:17:30 -07:00
_ID_RE = r ' [0-9a-f] { 32,34} '
2016-11-12 12:52:02 -08:00
class MediasiteIE ( InfoExtractor ) :
2019-03-30 11:17:30 -07:00
_VALID_URL = r ' (?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/(?:default|livebroadcast)/Presentation)/(?P<id> %s )(?P<query> \ ?[^#]+|) ' % _ID_RE
2016-11-12 12:52:02 -08:00
_TESTS = [
{
' url ' : ' https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d ' ,
' info_dict ' : {
' id ' : ' 2db6c271681e4f199af3c60d1f82869b1d ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Lecture: Tuesday, September 20, 2016 - Sir Andrew Wiles ' ,
' description ' : ' Sir Andrew Wiles: “Equations in arithmetic” \\ n \\ nI will describe some of the interactions between modern number theory and the problem of solving equations in rational numbers or integers \\ u0027. ' ,
' timestamp ' : 1474268400.0 ,
' upload_date ' : ' 20160919 ' ,
} ,
} ,
{
' url ' : ' http://mediasite.uib.no/Mediasite/Play/90bb363295d945d6b548c867d01181361d?catalog=a452b7df-9ae1-46b7-a3ba-aceeb285f3eb ' ,
' info_dict ' : {
' id ' : ' 90bb363295d945d6b548c867d01181361d ' ,
' ext ' : ' mp4 ' ,
' upload_date ' : ' 20150429 ' ,
' title ' : ' 5) IT-forum 2015-Dag 1 - Dungbeetle - How and why Rain created a tiny bug tracker for Unity ' ,
' timestamp ' : 1430311380.0 ,
} ,
} ,
{
' url ' : ' https://collegerama.tudelft.nl/Mediasite/Play/585a43626e544bdd97aeb71a0ec907a01d ' ,
' md5 ' : ' 481fda1c11f67588c0d9d8fbdced4e39 ' ,
' info_dict ' : {
' id ' : ' 585a43626e544bdd97aeb71a0ec907a01d ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Een nieuwe wereld: waarden, bewustzijn en techniek van de mensheid 2.0. ' ,
' description ' : ' ' ,
' thumbnail ' : r ' re:^https?://.* \ .jpg(?: \ ?.*)?$ ' ,
' duration ' : 7713.088 ,
' timestamp ' : 1413309600 ,
' upload_date ' : ' 20141014 ' ,
} ,
} ,
{
' url ' : ' https://collegerama.tudelft.nl/Mediasite/Play/86a9ea9f53e149079fbdb4202b521ed21d?catalog=fd32fd35-6c99-466c-89d4-cd3c431bc8a4 ' ,
' md5 ' : ' ef1fdded95bdf19b12c5999949419c92 ' ,
' info_dict ' : {
' id ' : ' 86a9ea9f53e149079fbdb4202b521ed21d ' ,
' ext ' : ' wmv ' ,
' title ' : ' 64ste Vakantiecursus: Afvalwater ' ,
' description ' : ' md5:7fd774865cc69d972f542b157c328305 ' ,
' thumbnail ' : r ' re:^https?://.* \ .jpg(?: \ ?.*?)?$ ' ,
' duration ' : 10853 ,
' timestamp ' : 1326446400 ,
' upload_date ' : ' 20120113 ' ,
} ,
} ,
{
' url ' : ' http://digitalops.sandia.gov/Mediasite/Play/24aace4429fc450fb5b38cdbf424a66e1d ' ,
' md5 ' : ' 9422edc9b9a60151727e4b6d8bef393d ' ,
' info_dict ' : {
' id ' : ' 24aace4429fc450fb5b38cdbf424a66e1d ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Xyce Software Training - Section 1 ' ,
' description ' : r ' re:(?s)SAND Number: SAND 2013-7800. { 200,} ' ,
' upload_date ' : ' 20120409 ' ,
' timestamp ' : 1333983600 ,
' duration ' : 7794 ,
}
2018-12-17 09:03:00 -08:00
} ,
{
' url ' : ' https://collegerama.tudelft.nl/Mediasite/Showcase/livebroadcast/Presentation/ada7020854f743c49fbb45c9ec7dbb351d ' ,
' only_matching ' : True ,
} ,
2018-12-17 10:55:13 -08:00
{
' url ' : ' https://mediasite.ntnu.no/Mediasite/Showcase/default/Presentation/7d8b913259334b688986e970fae6fcb31d ' ,
' only_matching ' : True ,
} ,
2016-11-12 12:52:02 -08:00
]
# look in Mediasite.Core.js (Mediasite.ContentStreamType[*])
_STREAM_TYPES = {
2017-12-29 16:28:18 -08:00
0 : ' video1 ' , # the main video
2016-11-12 12:52:02 -08:00
2 : ' slide ' ,
3 : ' presentation ' ,
2017-12-29 16:28:18 -08:00
4 : ' video2 ' , # screencast?
2016-11-12 12:52:02 -08:00
5 : ' video3 ' ,
}
2017-12-29 16:28:18 -08:00
@staticmethod
def _extract_urls ( webpage ) :
return [
unescapeHTML ( mobj . group ( ' url ' ) )
for mobj in re . finditer (
2019-03-30 11:17:30 -07:00
r ' (?xi)<iframe \ b[^>]+ \ bsrc=([ " \' ])(?P<url>(?:(?:https?:)?//[^/]+)?/Mediasite/Play/ %s (?: \ ?.*?)?) \ 1 ' % _ID_RE ,
2017-12-29 16:28:18 -08:00
webpage ) ]
2016-11-12 12:52:02 -08:00
def _real_extract ( self , url ) :
url , data = unsmuggle_url ( url , { } )
mobj = re . match ( self . _VALID_URL , url )
2017-12-29 16:28:18 -08:00
resource_id = mobj . group ( ' id ' )
query = mobj . group ( ' query ' )
2016-11-12 12:52:02 -08:00
2017-12-29 16:28:18 -08:00
webpage , urlh = self . _download_webpage_handle ( url , resource_id ) # XXX: add UrlReferrer?
redirect_url = compat_str ( urlh . geturl ( ) )
2016-11-12 12:52:02 -08:00
# XXX: might have also extracted UrlReferrer and QueryString from the html
2017-12-29 16:28:18 -08:00
service_path = compat_urlparse . urljoin ( redirect_url , self . _html_search_regex (
r ' <div[^>]+ \ bid=[ " \' ]ServicePath[^>]+>(.+?)</div> ' , webpage , resource_id ,
2016-11-12 12:52:02 -08:00
default = ' /Mediasite/PlayerService/PlayerService.svc/json ' ) )
2017-12-29 16:28:18 -08:00
player_options = self . _download_json (
' %s /GetPlayerOptions ' % service_path , resource_id ,
2016-11-12 12:52:02 -08:00
headers = {
' Content-type ' : ' application/json; charset=utf-8 ' ,
' X-Requested-With ' : ' XMLHttpRequest ' ,
} ,
data = json . dumps ( {
' getPlayerOptionsRequest ' : {
2017-12-29 16:28:18 -08:00
' ResourceId ' : resource_id ,
' QueryString ' : query ,
2016-11-12 12:52:02 -08:00
' UrlReferrer ' : data . get ( ' UrlReferrer ' , ' ' ) ,
' UseScreenReader ' : False ,
}
2017-12-29 16:28:18 -08:00
} ) . encode ( ' utf-8 ' ) ) [ ' d ' ]
presentation = player_options [ ' Presentation ' ]
title = presentation [ ' Title ' ]
if presentation is None :
raise ExtractorError (
' Mediasite says: %s ' % player_options [ ' PlayerPresentationStatusMessage ' ] ,
2016-11-12 12:52:02 -08:00
expected = True )
thumbnails = [ ]
formats = [ ]
2017-12-29 16:28:18 -08:00
for snum , Stream in enumerate ( presentation [ ' Streams ' ] ) :
stream_type = Stream . get ( ' StreamType ' )
if stream_type is None :
continue
video_urls = Stream . get ( ' VideoUrls ' )
if not isinstance ( video_urls , list ) :
video_urls = [ ]
stream_id = self . _STREAM_TYPES . get (
stream_type , ' type %u ' % stream_type )
2016-11-12 12:52:02 -08:00
stream_formats = [ ]
2017-12-29 16:28:18 -08:00
for unum , VideoUrl in enumerate ( video_urls ) :
2018-07-21 05:08:28 -07:00
video_url = url_or_none ( VideoUrl . get ( ' Location ' ) )
if not video_url :
2017-12-29 16:28:18 -08:00
continue
2016-11-12 12:52:02 -08:00
# XXX: if Stream.get('CanChangeScheme', False), switch scheme to HTTP/HTTPS
2017-12-29 16:28:18 -08:00
media_type = VideoUrl . get ( ' MediaType ' )
if media_type == ' SS ' :
2016-11-12 12:52:02 -08:00
stream_formats . extend ( self . _extract_ism_formats (
2017-12-29 16:28:18 -08:00
video_url , resource_id ,
ism_id = ' %s - %u . %u ' % ( stream_id , snum , unum ) ,
fatal = False ) )
elif media_type == ' Dash ' :
stream_formats . extend ( self . _extract_mpd_formats (
video_url , resource_id ,
mpd_id = ' %s - %u . %u ' % ( stream_id , snum , unum ) ,
fatal = False ) )
else :
stream_formats . append ( {
' format_id ' : ' %s - %u . %u ' % ( stream_id , snum , unum ) ,
' url ' : video_url ,
' ext ' : mimetype2ext ( VideoUrl . get ( ' MimeType ' ) ) ,
} )
2016-11-12 12:52:02 -08:00
# TODO: if Stream['HasSlideContent']:
# synthesise an MJPEG video stream '%s-%u.slides' % (stream_type, snum)
# from Stream['Slides']
# this will require writing a custom downloader...
# disprefer 'secondary' streams
2017-12-29 16:28:18 -08:00
if stream_type != 0 :
2016-11-12 12:52:02 -08:00
for fmt in stream_formats :
fmt [ ' preference ' ] = - 1
2017-12-29 16:28:18 -08:00
thumbnail_url = Stream . get ( ' ThumbnailUrl ' )
if thumbnail_url :
2016-11-12 12:52:02 -08:00
thumbnails . append ( {
2017-12-29 16:28:18 -08:00
' id ' : ' %s - %u ' % ( stream_id , snum ) ,
' url ' : urljoin ( redirect_url , thumbnail_url ) ,
' preference ' : - 1 if stream_type != 0 else 0 ,
2016-11-12 12:52:02 -08:00
} )
formats . extend ( stream_formats )
self . _sort_formats ( formats )
# XXX: Presentation['Presenters']
# XXX: Presentation['Transcript']
return {
2017-12-29 16:28:18 -08:00
' id ' : resource_id ,
' title ' : title ,
' description ' : presentation . get ( ' Description ' ) ,
' duration ' : float_or_none ( presentation . get ( ' Duration ' ) , 1000 ) ,
' timestamp ' : float_or_none ( presentation . get ( ' UnixTime ' ) , 1000 ) ,
2016-11-12 12:52:02 -08:00
' formats ' : formats ,
' thumbnails ' : thumbnails ,
}
2019-03-30 11:17:30 -07:00
class MediasiteCatalogIE ( InfoExtractor ) :
_VALID_URL = r ''' (?xi)
( ? P < url > https ? : / / [ ^ / ] + / Mediasite )
/ Catalog / Full /
( ? P < catalog_id > { 0 } )
( ? :
/ ( ? P < current_folder_id > { 0 } )
/ ( ? P < root_dynamic_folder_id > { 0 } )
) ?
''' .format(_ID_RE)
_TESTS = [ {
' url ' : ' http://events7.mediasite.com/Mediasite/Catalog/Full/631f9e48530d454381549f955d08c75e21 ' ,
' info_dict ' : {
' id ' : ' 631f9e48530d454381549f955d08c75e21 ' ,
' title ' : ' WCET Summit: Adaptive Learning in Higher Ed: Improving Outcomes Dynamically ' ,
} ,
' playlist_count ' : 6 ,
' expected_warnings ' : [ ' is not a supported codec ' ] ,
} , {
# with CurrentFolderId and RootDynamicFolderId
' url ' : ' https://medaudio.medicine.iu.edu/Mediasite/Catalog/Full/9518c4a6c5cf4993b21cbd53e828a92521/97a9db45f7ab47428c77cd2ed74bb98f14/9518c4a6c5cf4993b21cbd53e828a92521 ' ,
' info_dict ' : {
' id ' : ' 9518c4a6c5cf4993b21cbd53e828a92521 ' ,
' title ' : ' IUSM Family and Friends Sessions ' ,
} ,
' playlist_count ' : 2 ,
} , {
' url ' : ' http://uipsyc.mediasite.com/mediasite/Catalog/Full/d5d79287c75243c58c50fef50174ec1b21 ' ,
' only_matching ' : True ,
} , {
# no AntiForgeryToken
' url ' : ' https://live.libraries.psu.edu/Mediasite/Catalog/Full/8376d4b24dd1457ea3bfe4cf9163feda21 ' ,
' only_matching ' : True ,
} , {
' url ' : ' https://medaudio.medicine.iu.edu/Mediasite/Catalog/Full/9518c4a6c5cf4993b21cbd53e828a92521/97a9db45f7ab47428c77cd2ed74bb98f14/9518c4a6c5cf4993b21cbd53e828a92521 ' ,
' only_matching ' : True ,
} ]
def _real_extract ( self , url ) :
mobj = re . match ( self . _VALID_URL , url )
mediasite_url = mobj . group ( ' url ' )
catalog_id = mobj . group ( ' catalog_id ' )
current_folder_id = mobj . group ( ' current_folder_id ' ) or catalog_id
root_dynamic_folder_id = mobj . group ( ' root_dynamic_folder_id ' )
webpage = self . _download_webpage ( url , catalog_id )
# AntiForgeryToken is optional (e.g. [1])
# 1. https://live.libraries.psu.edu/Mediasite/Catalog/Full/8376d4b24dd1457ea3bfe4cf9163feda21
anti_forgery_token = self . _search_regex (
r ' AntiForgeryToken \ s*: \ s*([ " \' ])(?P<value>(?:(?! \ 1).)+) \ 1 ' ,
webpage , ' anti forgery token ' , default = None , group = ' value ' )
if anti_forgery_token :
anti_forgery_header = self . _search_regex (
r ' AntiForgeryHeaderName \ s*: \ s*([ " \' ])(?P<value>(?:(?! \ 1).)+) \ 1 ' ,
webpage , ' anti forgery header name ' ,
default = ' X-SOFO-AntiForgeryHeader ' , group = ' value ' )
data = {
' IsViewPage ' : True ,
' IsNewFolder ' : True ,
' AuthTicket ' : None ,
' CatalogId ' : catalog_id ,
' CurrentFolderId ' : current_folder_id ,
' RootDynamicFolderId ' : root_dynamic_folder_id ,
' ItemsPerPage ' : 1000 ,
' PageIndex ' : 0 ,
' PermissionMask ' : ' Execute ' ,
' CatalogSearchType ' : ' SearchInFolder ' ,
' SortBy ' : ' Date ' ,
' SortDirection ' : ' Descending ' ,
' StartDate ' : None ,
' EndDate ' : None ,
' StatusFilterList ' : None ,
' PreviewKey ' : None ,
' Tags ' : [ ] ,
}
headers = {
' Content-Type ' : ' application/json; charset=UTF-8 ' ,
' Referer ' : url ,
' X-Requested-With ' : ' XMLHttpRequest ' ,
}
if anti_forgery_token :
headers [ anti_forgery_header ] = anti_forgery_token
catalog = self . _download_json (
' %s /Catalog/Data/GetPresentationsForFolder ' % mediasite_url ,
catalog_id , data = json . dumps ( data ) . encode ( ) , headers = headers )
entries = [ ]
for video in catalog [ ' PresentationDetailsList ' ] :
if not isinstance ( video , dict ) :
continue
video_id = str_or_none ( video . get ( ' Id ' ) )
if not video_id :
continue
entries . append ( self . url_result (
' %s /Play/ %s ' % ( mediasite_url , video_id ) ,
ie = MediasiteIE . ie_key ( ) , video_id = video_id ) )
title = try_get (
catalog , lambda x : x [ ' CurrentFolder ' ] [ ' Name ' ] , compat_str )
return self . playlist_result ( entries , catalog_id , title , )