1 from .common
import InfoExtractor
11 class TubeTuGrazBaseIE(InfoExtractor
):
12 _NETRC_MACHINE
= 'tubetugraz'
14 _API_EPISODE
= 'https://tube.tugraz.at/search/episode.json'
15 _FORMAT_TYPES
= ('presentation', 'presenter')
17 def _perform_login(self
, username
, password
):
18 urlh
= self
._request
_webpage
(
19 'https://tube.tugraz.at/Shibboleth.sso/Login?target=/paella/ui/index.html',
20 None, fatal
=False, note
='downloading login page', errnote
='unable to fetch login page')
24 content
, urlh
= self
._download
_webpage
_handle
(
25 urlh
.url
, None, fatal
=False, headers
={'referer': urlh.url}
,
26 note
='logging in', errnote
='unable to log in',
27 data
=urlencode_postdata({
29 '_eventId_proceed': '',
30 'j_username': username
,
31 'j_password': password
33 if not urlh
or urlh
.url
== 'https://tube.tugraz.at/paella/ui/index.html':
36 if not self
._html
_search
_regex
(
37 r
'<p\b[^>]*>(Bitte geben Sie einen OTP-Wert ein:)</p>',
38 content
, 'TFA prompt', default
=None):
39 self
.report_warning('unable to login: incorrect password')
42 content
, urlh
= self
._download
_webpage
_handle
(
43 urlh
.url
, None, fatal
=False, headers
={'referer': urlh.url}
,
44 note
='logging in with TFA', errnote
='unable to log in with TFA',
45 data
=urlencode_postdata({
47 '_eventId_proceed': '',
48 'j_tokenNumber': self
._get
_tfa
_info
(),
50 if not urlh
or urlh
.url
== 'https://tube.tugraz.at/paella/ui/index.html':
53 self
.report_warning('unable to login: incorrect TFA code')
55 def _extract_episode(self
, episode_info
):
56 id = episode_info
.get('id')
57 formats
= list(self
._extract
_formats
(
58 traverse_obj(episode_info
, ('mediapackage', 'media', 'track')), id))
60 title
= traverse_obj(episode_info
, ('mediapackage', 'title'), 'dcTitle')
61 series_title
= traverse_obj(episode_info
, ('mediapackage', 'seriestitle'))
62 creator
= ', '.join(variadic(traverse_obj(
63 episode_info
, ('mediapackage', 'creators', 'creator'), 'dcCreator', default
='')))
67 'creator': creator
or None,
68 'duration': traverse_obj(episode_info
, ('mediapackage', 'duration'), 'dcExtent'),
69 'series': series_title
,
70 'series_id': traverse_obj(episode_info
, ('mediapackage', 'series'), 'dcIsPartOf'),
71 'episode': series_title
and title
,
75 def _set_format_type(self
, formats
, type):
77 f
['format_note'] = type
78 if not type.startswith(self
._FORMAT
_TYPES
[0]):
82 def _extract_formats(self
, format_list
, id):
83 has_hls
, has_dash
= False, False
85 for format_info
in format_list
or []:
86 url
= traverse_obj(format_info
, ('tags', 'url'), 'url')
90 type = format_info
.get('type') or 'unknown'
91 transport
= (format_info
.get('transport') or 'https').lower()
93 if transport
== 'https':
96 'abr': float_or_none(traverse_obj(format_info
, ('audio', 'bitrate')), 1000),
97 'vbr': float_or_none(traverse_obj(format_info
, ('video', 'bitrate')), 1000),
98 'fps': traverse_obj(format_info
, ('video', 'framerate')),
99 **parse_resolution(traverse_obj(format_info
, ('video', 'resolution'))),
101 elif transport
== 'hls':
102 has_hls
, formats
= True, self
._extract
_m
3u8_formats
(
103 url
, id, 'mp4', fatal
=False, note
=f
'downloading {type} HLS manifest')
104 elif transport
== 'dash':
105 has_dash
, formats
= True, self
._extract
_mpd
_formats
(
106 url
, id, fatal
=False, note
=f
'downloading {type} DASH manifest')
108 # RTMP, HDS, SMOOTH, and unknown formats
109 # - RTMP url fails on every tested entry until now
110 # - HDS url 404's on every tested entry until now
111 # - SMOOTH url 404's on every tested entry until now
114 yield from self
._set
_format
_type
(formats
, type)
116 # TODO: Add test for these
117 for type in self
._FORMAT
_TYPES
:
119 hls_formats
= self
._extract
_m
3u8_formats
(
120 f
'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{id}_{type}.smil/playlist.m3u8',
121 id, 'mp4', fatal
=False, note
=f
'Downloading {type} HLS manifest', errnote
=False) or []
122 yield from self
._set
_format
_type
(hls_formats
, type)
125 dash_formats
= self
._extract
_mpd
_formats
(
126 f
'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{id}_{type}.smil/manifest_mpm4sav_mvlist.mpd',
127 id, fatal
=False, note
=f
'Downloading {type} DASH manifest', errnote
=False)
128 yield from self
._set
_format
_type
(dash_formats
, type)
131 class TubeTuGrazIE(TubeTuGrazBaseIE
):
132 IE_DESC
= 'tube.tugraz.at'
134 _VALID_URL
= r
'''(?x)
135 https?://tube\.tugraz\.at/paella/ui/watch.html\?id=
136 (?P<id>[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})
140 'url': 'https://tube.tugraz.at/paella/ui/watch.html?id=f2634392-e40e-4ac7-9ddc-47764aa23d40',
141 'md5': 'a23a3d5c9aaca2b84932fdba66e17145',
143 'id': 'f2634392-e40e-4ac7-9ddc-47764aa23d40',
145 'title': '#6 (23.11.2017)',
146 'episode': '#6 (23.11.2017)',
147 'series': '[INB03001UF] Einführung in die strukturierte Programmierung',
148 'creator': 'Safran C',
150 'series_id': 'b1192fff-2aa7-4bf0-a5cf-7b15c3bd3b34',
153 'url': 'https://tube.tugraz.at/paella/ui/watch.html?id=2df6d787-e56a-428d-8ef4-d57f07eef238',
154 'md5': 'de0d854a56bf7318d2b693fe1adb89a5',
156 'id': '2df6d787-e56a-428d-8ef4-d57f07eef238',
157 'title': 'TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238',
160 'expected_warnings': ['Extractor failed to obtain "title"'],
164 def _real_extract(self
, url
):
165 video_id
= self
._match
_id
(url
)
166 episode_data
= self
._download
_json
(
167 self
._API
_EPISODE
, video_id
, query
={'id': video_id, 'limit': 1}
, note
='Downloading episode metadata')
169 episode_info
= traverse_obj(episode_data
, ('search-results', 'result'), default
={'id': video_id}
)
170 return self
._extract
_episode
(episode_info
)
173 class TubeTuGrazSeriesIE(TubeTuGrazBaseIE
):
174 _VALID_URL
= r
'''(?x)
175 https?://tube\.tugraz\.at/paella/ui/browse\.html\?series=
176 (?P<id>[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})
179 'url': 'https://tube.tugraz.at/paella/ui/browse.html?series=0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
180 'id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
182 'id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
183 'title': '[209351] Strassenwesen',
188 'id': 'ee17ce5d-34e2-48b7-a76a-fed148614e11',
189 'series_id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
191 'title': '#4 Detailprojekt',
192 'episode': '#4 Detailprojekt',
193 'series': '[209351] Strassenwesen',
194 'creator': 'Neuhold R',
200 'id': '87350498-799a-44d3-863f-d1518a98b114',
201 'series_id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
203 'title': '#3 Generelles Projekt',
204 'episode': '#3 Generelles Projekt',
205 'series': '[209351] Strassenwesen',
206 'creator': 'Neuhold R',
212 'id': '778599ea-489e-4189-9e05-3b4888e19bcd',
213 'series_id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
215 'title': '#2 Vorprojekt',
216 'episode': '#2 Vorprojekt',
217 'series': '[209351] Strassenwesen',
218 'creator': 'Neuhold R',
224 'id': '75e4c71c-d99d-4e56-b0e6-4f2bcdf11f29',
225 'series_id': '0e6351b7-c372-491e-8a49-2c9b7e21c5a6',
227 'title': '#1 Variantenstudium',
228 'episode': '#1 Variantenstudium',
229 'series': '[209351] Strassenwesen',
230 'creator': 'Neuhold R',
235 'min_playlist_count': 4
238 def _real_extract(self
, url
):
239 id = self
._match
_id
(url
)
240 episodes_data
= self
._download
_json
(self
._API
_EPISODE
, id, query
={'sid': id}
, note
='Downloading episode list')
241 series_data
= self
._download
_json
(
242 'https://tube.tugraz.at/series/series.json', id, fatal
=False,
243 note
='downloading series metadata', errnote
='failed to download series metadata',
250 return self
.playlist_result(
251 map(self
._extract
_episode
, episodes_data
['search-results']['result']), id,
252 traverse_obj(series_data
, ('catalogs', 0, 'http://purl.org/dc/terms/', 'title', 0, 'value')))