1 from __future__
import unicode_literals
6 from .common
import InfoExtractor
22 class UstreamIE(InfoExtractor
):
23 _VALID_URL
= r
'https?://(?:www\.)?(?:ustream\.tv|video\.ibm\.com)/(?P<type>recorded|embed|embed/recorded)/(?P<id>\d+)'
26 'url': 'http://www.ustream.tv/recorded/20274954',
27 'md5': '088f151799e8f572f84eb62f17d73e5c',
31 'title': 'Young Americans for Liberty February 7, 2012 2:28 AM',
32 'description': 'Young Americans for Liberty February 7, 2012 2:28 AM',
33 'timestamp': 1328577035,
34 'upload_date': '20120207',
35 'uploader': 'yaliberty',
36 'uploader_id': '6780869',
39 # From http://sportscanada.tv/canadagames/index.php/week2/figure-skating/444
40 # Title and uploader available only from params JSON
41 'url': 'http://www.ustream.tv/embed/recorded/59307601?ub=ff0000&lc=ff0000&oc=ffffff&uc=ffffff&v=3&wmode=direct',
42 'md5': '5a2abf40babeac9812ed20ae12d34e10',
46 'title': '-CG11- Canada Games Figure Skating',
47 'uploader': 'sportscanadatv',
49 'skip': 'This Pro Broadcaster has chosen to remove this video from the ustream.tv site.',
51 'url': 'http://www.ustream.tv/embed/10299409',
57 'url': 'http://www.ustream.tv/recorded/91343263',
61 'title': 'GitHub Universe - General Session - Day 1',
62 'upload_date': '20160914',
63 'description': 'GitHub Universe - General Session - Day 1',
64 'timestamp': 1473872730,
65 'uploader': 'wa0dnskeqkr',
66 'uploader_id': '38977840',
69 'skip_download': True, # m3u8 download
72 'url': 'https://video.ibm.com/embed/recorded/128240221?&autoplay=true&controls=true&volume=100',
73 'only_matching': True,
77 def _extract_url(webpage
):
79 r
'<iframe[^>]+?src=(["\'])(?P
<url
>https?
://(?
:www\
.)?
(?
:ustream\
.tv|video\
.ibm\
.com
)/embed
/.+?
)\
1', webpage)
81 return mobj.group('url
')
83 def _get_stream_info(self, url, video_id, app_id_ver, extra_note=None):
87 rnd = random.randrange
92 conn_info = self._download_json(
93 'http
://r
%d-1-%s-recorded-lp
-live
.ums
.ustream
.tv
/1/ustream
' % (rnd(1e8), video_id),
94 video_id, note='Downloading connection info
' + extra_note,
97 'appId
': app_id_ver[0],
98 'appVersion
': app_id_ver[1],
99 'rsid
': '%s:%s' % (num_to_hex(rnd(1e8)), num_to_hex(rnd(1e8))),
100 'rpin
': '_rpin
.%d' % rnd(1e15),
103 'application
': 'recorded
',
105 host = conn_info[0]['args
'][0]['host
']
106 connection_id = conn_info[0]['args
'][0]['connectionId
']
108 return self._download_json(
109 'http
://%s/1/ustream?connectionId
=%s' % (host, connection_id),
110 video_id, note='Downloading stream info
' + extra_note)
112 def _get_streams(self, url, video_id, app_id_ver):
113 # Sometimes the return dict does not have 'stream
'
114 for trial_count in range(3):
115 stream_info = self._get_stream_info(
116 url, video_id, app_id_ver,
117 extra_note=' (try %d)' % (trial_count + 1) if trial_count > 0 else '')
118 if 'stream
' in stream_info[0]['args
'][0]:
119 return stream_info[0]['args
'][0]['stream
']
122 def _parse_segmented_mp4(self, dash_stream_info):
123 def resolve_dash_template(template, idx, chunk_hash):
124 return template.replace('%', compat_str(idx), 1).replace('%', chunk_hash)
127 for stream in dash_stream_info['streams
']:
128 # Use only one provider to avoid too many formats
129 provider = dash_stream_info['providers
'][0]
131 'url
': resolve_dash_template(
132 provider['url
'] + stream['initUrl
'], 0, dash_stream_info['hashes
']['0'])
134 for idx in range(dash_stream_info['videoLength
'] // dash_stream_info['chunkTime
']):
136 'url
': resolve_dash_template(
137 provider['url
'] + stream['segmentUrl
'], idx,
138 dash_stream_info['hashes
'][compat_str(idx // 10 * 10)])
140 content_type = stream['contentType
']
141 kind = content_type.split('/')[0]
143 'format_id
': join_nonempty(
144 'dash
', kind, str_or_none(stream.get('bitrate
'))),
145 'protocol
': 'http_dash_segments
',
146 # TODO: generate a MPD doc for external players?
147 'url
': encode_data_uri(b'<MPD
/>', 'text
/xml
'),
148 'ext
': mimetype2ext(content_type),
149 'height
': stream.get('height
'),
150 'width
': stream.get('width
'),
151 'fragments
': fragments,
155 'vcodec
': stream.get('codec
'),
157 'vbr
': stream.get('bitrate
'),
162 'acodec
': stream.get('codec
'),
163 'abr
': stream.get('bitrate
'),
168 def _real_extract(self, url):
169 m = self._match_valid_url(url)
170 video_id = m.group('id')
172 # some sites use this embed format (see: https://github.com/ytdl-org/youtube-dl/issues/2990)
173 if m.group('type') == 'embed
/recorded
':
174 video_id = m.group('id')
175 desktop_url = 'http
://www
.ustream
.tv
/recorded
/' + video_id
176 return self.url_result(desktop_url, 'Ustream
')
177 if m.group('type') == 'embed
':
178 video_id = m.group('id')
179 webpage = self._download_webpage(url, video_id)
180 content_video_ids = self._parse_json(self._search_regex(
181 r'ustream\
.vars\
.offAirContentVideoIds
=([^
;]+);', webpage,
182 'content video IDs
'), video_id)
183 return self.playlist_result(
184 map(lambda u: self.url_result('http
://www
.ustream
.tv
/recorded
/' + u, 'Ustream
'), content_video_ids),
187 params = self._download_json(
188 'https
://api
.ustream
.tv
/videos
/%s.json
' % video_id, video_id)
190 error = params.get('error
')
192 raise ExtractorError(
193 '%s returned error
: %s' % (self.IE_NAME, error), expected=True)
195 video = params['video
']
197 title = video['title
']
198 filesize = float_or_none(video.get('file_size
'))
204 'filesize
': filesize,
205 } for format_id, video_url in video['media_urls
'].items() if video_url]
208 hls_streams = self._get_streams(url, video_id, app_id_ver=(11, 2))
210 # m3u8_native leads to intermittent ContentTooShortError
211 formats.extend(self._extract_m3u8_formats(
212 hls_streams[0]['url
'], video_id, ext='mp4
', m3u8_id='hls
'))
215 # DASH streams handling is incomplete as 'url
' is missing
216 dash_streams = self._get_streams(url, video_id, app_id_ver=(3, 1))
218 formats.extend(self._parse_segmented_mp4(dash_streams))
221 self._sort_formats(formats)
223 description = video.get('description
')
224 timestamp = int_or_none(video.get('created_at
'))
225 duration = float_or_none(video.get('length
'))
226 view_count = int_or_none(video.get('views
'))
228 uploader = video.get('owner
', {}).get('username
')
229 uploader_id = video.get('owner
', {}).get('id')
233 'url
': thumbnail_url,
234 } for thumbnail_id, thumbnail_url in video.get('thumbnail
', {}).items()]
239 'description
': description,
240 'thumbnails
': thumbnails,
241 'timestamp
': timestamp,
242 'duration
': duration,
243 'view_count
': view_count,
244 'uploader
': uploader,
245 'uploader_id
': uploader_id,
250 class UstreamChannelIE(InfoExtractor):
251 _VALID_URL = r'https?
://(?
:www\
.)?ustream\
.tv
/channel
/(?P
<slug
>.+)'
252 IE_NAME = 'ustream
:channel
'
254 'url
': 'http
://www
.ustream
.tv
/channel
/channeljapan
',
258 'playlist_mincount
': 17,
261 def _real_extract(self, url):
262 m = self._match_valid_url(url)
263 display_id = m.group('slug
')
264 webpage = self._download_webpage(url, display_id)
265 channel_id = self._html_search_meta('ustream
:channel_id
', webpage)
267 BASE = 'http
://www
.ustream
.tv
'
268 next_url = '/ajax
/socialstream
/videos
/%s/1.json
' % channel_id
271 reply = self._download_json(
272 compat_urlparse.urljoin(BASE, next_url), display_id,
273 note='Downloading video
information (next
: %d)' % (len(video_ids) + 1))
274 video_ids.extend(re.findall(r'data
-content
-id="(\d.*)"', reply['data
']))
275 next_url = reply['nextUrl
']
278 self.url_result('http
://www
.ustream
.tv
/recorded
/' + vid, 'Ustream
')
279 for vid in video_ids]
283 'display_id
': display_id,