tvplay.py (18176B)
1 # coding: utf-8 2 from __future__ import unicode_literals 3 4 import re 5 6 from .common import InfoExtractor 7 from ..compat import ( 8 compat_HTTPError, 9 compat_urlparse, 10 ) 11 from ..utils import ( 12 determine_ext, 13 ExtractorError, 14 int_or_none, 15 parse_duration, 16 parse_iso8601, 17 qualities, 18 try_get, 19 update_url_query, 20 url_or_none, 21 urljoin, 22 ) 23 24 25 class TVPlayIE(InfoExtractor): 26 IE_NAME = 'mtg' 27 IE_DESC = 'MTG services' 28 _VALID_URL = r'''(?x) 29 (?: 30 mtg:| 31 https?:// 32 (?:www\.)? 33 (?: 34 tvplay(?:\.skaties)?\.lv(?:/parraides)?| 35 (?:tv3play|play\.tv3)\.lt(?:/programos)?| 36 tv3play(?:\.tv3)?\.ee/sisu| 37 (?:tv(?:3|6|8|10)play|viafree)\.se/program| 38 (?:(?:tv3play|viasat4play|tv6play|viafree)\.no|(?:tv3play|viafree)\.dk)/programmer| 39 play\.nova(?:tv)?\.bg/programi 40 ) 41 /(?:[^/]+/)+ 42 ) 43 (?P<id>\d+) 44 ''' 45 _TESTS = [ 46 { 47 'url': 'http://www.tvplay.lv/parraides/vinas-melo-labak/418113?autostart=true', 48 'md5': 'a1612fe0849455423ad8718fe049be21', 49 'info_dict': { 50 'id': '418113', 51 'ext': 'mp4', 52 'title': 'Kādi ir īri? - Viņas melo labāk', 53 'description': 'Baiba apsmej īrus, kādi tie ir un ko viņi dara.', 54 'series': 'Viņas melo labāk', 55 'season': '2.sezona', 56 'season_number': 2, 57 'duration': 25, 58 'timestamp': 1406097056, 59 'upload_date': '20140723', 60 }, 61 }, 62 { 63 'url': 'http://play.tv3.lt/programos/moterys-meluoja-geriau/409229?autostart=true', 64 'info_dict': { 65 'id': '409229', 66 'ext': 'flv', 67 'title': 'Moterys meluoja geriau', 68 'description': 'md5:9aec0fc68e2cbc992d2a140bd41fa89e', 69 'series': 'Moterys meluoja geriau', 70 'episode_number': 47, 71 'season': '1 sezonas', 72 'season_number': 1, 73 'duration': 1330, 74 'timestamp': 1403769181, 75 'upload_date': '20140626', 76 }, 77 'params': { 78 # rtmp download 79 'skip_download': True, 80 }, 81 }, 82 { 83 'url': 'http://www.tv3play.ee/sisu/kodu-keset-linna/238551?autostart=true', 84 'info_dict': { 85 'id': '238551', 86 'ext': 'flv', 87 'title': 'Kodu keset linna 398537', 88 'description': 'md5:7df175e3c94db9e47c0d81ffa5d68701', 89 'duration': 1257, 90 'timestamp': 1292449761, 91 'upload_date': '20101215', 92 }, 93 'params': { 94 # rtmp download 95 'skip_download': True, 96 }, 97 }, 98 { 99 'url': 'http://www.tv3play.se/program/husraddarna/395385?autostart=true', 100 'info_dict': { 101 'id': '395385', 102 'ext': 'mp4', 103 'title': 'Husräddarna S02E07', 104 'description': 'md5:f210c6c89f42d4fc39faa551be813777', 105 'duration': 2574, 106 'timestamp': 1400596321, 107 'upload_date': '20140520', 108 }, 109 'params': { 110 'skip_download': True, 111 }, 112 }, 113 { 114 'url': 'http://www.tv6play.se/program/den-sista-dokusapan/266636?autostart=true', 115 'info_dict': { 116 'id': '266636', 117 'ext': 'mp4', 118 'title': 'Den sista dokusåpan S01E08', 119 'description': 'md5:295be39c872520221b933830f660b110', 120 'duration': 1492, 121 'timestamp': 1330522854, 122 'upload_date': '20120229', 123 'age_limit': 18, 124 }, 125 'params': { 126 'skip_download': True, 127 }, 128 }, 129 { 130 'url': 'http://www.tv8play.se/program/antikjakten/282756?autostart=true', 131 'info_dict': { 132 'id': '282756', 133 'ext': 'mp4', 134 'title': 'Antikjakten S01E10', 135 'description': 'md5:1b201169beabd97e20c5ad0ad67b13b8', 136 'duration': 2646, 137 'timestamp': 1348575868, 138 'upload_date': '20120925', 139 }, 140 'params': { 141 'skip_download': True, 142 }, 143 }, 144 { 145 'url': 'http://www.tv3play.no/programmer/anna-anka-soker-assistent/230898?autostart=true', 146 'info_dict': { 147 'id': '230898', 148 'ext': 'mp4', 149 'title': 'Anna Anka søker assistent - Ep. 8', 150 'description': 'md5:f80916bf5bbe1c5f760d127f8dd71474', 151 'duration': 2656, 152 'timestamp': 1277720005, 153 'upload_date': '20100628', 154 }, 155 'params': { 156 'skip_download': True, 157 }, 158 }, 159 { 160 'url': 'http://www.viasat4play.no/programmer/budbringerne/21873?autostart=true', 161 'info_dict': { 162 'id': '21873', 163 'ext': 'mp4', 164 'title': 'Budbringerne program 10', 165 'description': 'md5:4db78dc4ec8a85bb04fd322a3ee5092d', 166 'duration': 1297, 167 'timestamp': 1254205102, 168 'upload_date': '20090929', 169 }, 170 'params': { 171 'skip_download': True, 172 }, 173 }, 174 { 175 'url': 'http://www.tv6play.no/programmer/hotelinspektor-alex-polizzi/361883?autostart=true', 176 'info_dict': { 177 'id': '361883', 178 'ext': 'mp4', 179 'title': 'Hotelinspektør Alex Polizzi - Ep. 10', 180 'description': 'md5:3ecf808db9ec96c862c8ecb3a7fdaf81', 181 'duration': 2594, 182 'timestamp': 1393236292, 183 'upload_date': '20140224', 184 }, 185 'params': { 186 'skip_download': True, 187 }, 188 }, 189 { 190 'url': 'http://play.novatv.bg/programi/zdravei-bulgariya/624952?autostart=true', 191 'info_dict': { 192 'id': '624952', 193 'ext': 'flv', 194 'title': 'Здравей, България (12.06.2015 г.) ', 195 'description': 'md5:99f3700451ac5bb71a260268b8daefd7', 196 'duration': 8838, 197 'timestamp': 1434100372, 198 'upload_date': '20150612', 199 }, 200 'params': { 201 # rtmp download 202 'skip_download': True, 203 }, 204 }, 205 { 206 'url': 'https://play.nova.bg/programi/zdravei-bulgariya/764300?autostart=true', 207 'only_matching': True, 208 }, 209 { 210 'url': 'http://tvplay.skaties.lv/parraides/vinas-melo-labak/418113?autostart=true', 211 'only_matching': True, 212 }, 213 { 214 'url': 'https://tvplay.skaties.lv/vinas-melo-labak/418113/?autostart=true', 215 'only_matching': True, 216 }, 217 { 218 # views is null 219 'url': 'http://tvplay.skaties.lv/parraides/tv3-zinas/760183', 220 'only_matching': True, 221 }, 222 { 223 'url': 'http://tv3play.tv3.ee/sisu/kodu-keset-linna/238551?autostart=true', 224 'only_matching': True, 225 }, 226 { 227 'url': 'http://www.viafree.se/program/underhallning/i-like-radio-live/sasong-1/676869', 228 'only_matching': True, 229 }, 230 { 231 'url': 'mtg:418113', 232 'only_matching': True, 233 } 234 ] 235 236 def _real_extract(self, url): 237 video_id = self._match_id(url) 238 geo_country = self._search_regex( 239 r'https?://[^/]+\.([a-z]{2})', url, 240 'geo country', default=None) 241 if geo_country: 242 self._initialize_geo_bypass({'countries': [geo_country.upper()]}) 243 video = self._download_json( 244 'http://playapi.mtgx.tv/v3/videos/%s' % video_id, video_id, 'Downloading video JSON') 245 246 title = video['title'] 247 248 try: 249 streams = self._download_json( 250 'http://playapi.mtgx.tv/v3/videos/stream/%s' % video_id, 251 video_id, 'Downloading streams JSON') 252 except ExtractorError as e: 253 if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: 254 msg = self._parse_json(e.cause.read().decode('utf-8'), video_id) 255 raise ExtractorError(msg['msg'], expected=True) 256 raise 257 258 quality = qualities(['hls', 'medium', 'high']) 259 formats = [] 260 for format_id, video_url in streams.get('streams', {}).items(): 261 video_url = url_or_none(video_url) 262 if not video_url: 263 continue 264 ext = determine_ext(video_url) 265 if ext == 'f4m': 266 formats.extend(self._extract_f4m_formats( 267 update_url_query(video_url, { 268 'hdcore': '3.5.0', 269 'plugin': 'aasp-3.5.0.151.81' 270 }), video_id, f4m_id='hds', fatal=False)) 271 elif ext == 'm3u8': 272 formats.extend(self._extract_m3u8_formats( 273 video_url, video_id, 'mp4', 'm3u8_native', 274 m3u8_id='hls', fatal=False)) 275 else: 276 fmt = { 277 'format_id': format_id, 278 'quality': quality(format_id), 279 'ext': ext, 280 } 281 if video_url.startswith('rtmp'): 282 m = re.search( 283 r'^(?P<url>rtmp://[^/]+/(?P<app>[^/]+))/(?P<playpath>.+)$', video_url) 284 if not m: 285 continue 286 fmt.update({ 287 'ext': 'flv', 288 'url': m.group('url'), 289 'app': m.group('app'), 290 'play_path': m.group('playpath'), 291 'preference': -1, 292 }) 293 else: 294 fmt.update({ 295 'url': video_url, 296 }) 297 formats.append(fmt) 298 299 if not formats and video.get('is_geo_blocked'): 300 self.raise_geo_restricted( 301 'This content might not be available in your country due to copyright reasons') 302 303 self._sort_formats(formats) 304 305 # TODO: webvtt in m3u8 306 subtitles = {} 307 sami_path = video.get('sami_path') 308 if sami_path: 309 lang = self._search_regex( 310 r'_([a-z]{2})\.xml', sami_path, 'lang', 311 default=compat_urlparse.urlparse(url).netloc.rsplit('.', 1)[-1]) 312 subtitles[lang] = [{ 313 'url': sami_path, 314 }] 315 316 series = video.get('format_title') 317 episode_number = int_or_none(video.get('format_position', {}).get('episode')) 318 season = video.get('_embedded', {}).get('season', {}).get('title') 319 season_number = int_or_none(video.get('format_position', {}).get('season')) 320 321 return { 322 'id': video_id, 323 'title': title, 324 'description': video.get('description'), 325 'series': series, 326 'episode_number': episode_number, 327 'season': season, 328 'season_number': season_number, 329 'duration': int_or_none(video.get('duration')), 330 'timestamp': parse_iso8601(video.get('created_at')), 331 'view_count': try_get(video, lambda x: x['views']['total'], int), 332 'age_limit': int_or_none(video.get('age_limit', 0)), 333 'formats': formats, 334 'subtitles': subtitles, 335 } 336 337 338 class ViafreeIE(InfoExtractor): 339 _VALID_URL = r'''(?x) 340 https?:// 341 (?:www\.)? 342 viafree\.(?P<country>dk|no|se) 343 /(?P<id>program(?:mer)?/(?:[^/]+/)+[^/?#&]+) 344 ''' 345 _TESTS = [{ 346 'url': 'http://www.viafree.no/programmer/underholdning/det-beste-vorspielet/sesong-2/episode-1', 347 'info_dict': { 348 'id': '757786', 349 'ext': 'mp4', 350 'title': 'Det beste vorspielet - Sesong 2 - Episode 1', 351 'description': 'md5:b632cb848331404ccacd8cd03e83b4c3', 352 'series': 'Det beste vorspielet', 353 'season_number': 2, 354 'duration': 1116, 355 'timestamp': 1471200600, 356 'upload_date': '20160814', 357 }, 358 'params': { 359 'skip_download': True, 360 }, 361 }, { 362 # with relatedClips 363 'url': 'http://www.viafree.se/program/reality/sommaren-med-youtube-stjarnorna/sasong-1/avsnitt-1', 364 'only_matching': True, 365 }, { 366 # Different og:image URL schema 367 'url': 'http://www.viafree.se/program/reality/sommaren-med-youtube-stjarnorna/sasong-1/avsnitt-2', 368 'only_matching': True, 369 }, { 370 'url': 'http://www.viafree.se/program/livsstil/husraddarna/sasong-2/avsnitt-2', 371 'only_matching': True, 372 }, { 373 'url': 'http://www.viafree.dk/programmer/reality/paradise-hotel/saeson-7/episode-5', 374 'only_matching': True, 375 }] 376 _GEO_BYPASS = False 377 378 @classmethod 379 def suitable(cls, url): 380 return False if TVPlayIE.suitable(url) else super(ViafreeIE, cls).suitable(url) 381 382 def _real_extract(self, url): 383 country, path = re.match(self._VALID_URL, url).groups() 384 content = self._download_json( 385 'https://viafree-content.mtg-api.com/viafree-content/v1/%s/path/%s' % (country, path), path) 386 program = content['_embedded']['viafreeBlocks'][0]['_embedded']['program'] 387 guid = program['guid'] 388 meta = content['meta'] 389 title = meta['title'] 390 391 try: 392 stream_href = self._download_json( 393 program['_links']['streamLink']['href'], guid, 394 headers=self.geo_verification_headers())['embedded']['prioritizedStreams'][0]['links']['stream']['href'] 395 except ExtractorError as e: 396 if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: 397 self.raise_geo_restricted(countries=[country]) 398 raise 399 400 formats = self._extract_m3u8_formats(stream_href, guid, 'mp4') 401 self._sort_formats(formats) 402 episode = program.get('episode') or {} 403 404 return { 405 'id': guid, 406 'title': title, 407 'thumbnail': meta.get('image'), 408 'description': meta.get('description'), 409 'series': episode.get('seriesTitle'), 410 'episode_number': int_or_none(episode.get('episodeNumber')), 411 'season_number': int_or_none(episode.get('seasonNumber')), 412 'duration': int_or_none(try_get(program, lambda x: x['video']['duration']['milliseconds']), 1000), 413 'timestamp': parse_iso8601(try_get(program, lambda x: x['availability']['start'])), 414 'formats': formats, 415 } 416 417 418 class TVPlayHomeIE(InfoExtractor): 419 _VALID_URL = r'https?://(?:tv3?)?play\.(?:tv3\.lt|skaties\.lv|tv3\.ee)/(?:[^/]+/)*[^/?#&]+-(?P<id>\d+)' 420 _TESTS = [{ 421 'url': 'https://tvplay.tv3.lt/aferistai-n-7/aferistai-10047125/', 422 'info_dict': { 423 'id': '366367', 424 'ext': 'mp4', 425 'title': 'Aferistai', 426 'description': 'Aferistai. Kalėdinė pasaka.', 427 'series': 'Aferistai [N-7]', 428 'season': '1 sezonas', 429 'season_number': 1, 430 'duration': 464, 431 'timestamp': 1394209658, 432 'upload_date': '20140307', 433 'age_limit': 18, 434 }, 435 'params': { 436 'skip_download': True, 437 }, 438 }, { 439 'url': 'https://tvplay.skaties.lv/vinas-melo-labak/vinas-melo-labak-10280317/', 440 'only_matching': True, 441 }, { 442 'url': 'https://tvplay.tv3.ee/cool-d-ga-mehhikosse/cool-d-ga-mehhikosse-10044354/', 443 'only_matching': True, 444 }, { 445 'url': 'https://play.tv3.lt/aferistai-10047125', 446 'only_matching': True, 447 }, { 448 'url': 'https://tv3play.skaties.lv/vinas-melo-labak-10280317', 449 'only_matching': True, 450 }, { 451 'url': 'https://play.tv3.ee/cool-d-ga-mehhikosse-10044354', 452 'only_matching': True, 453 }] 454 455 def _real_extract(self, url): 456 video_id = self._match_id(url) 457 458 asset = self._download_json( 459 urljoin(url, '/sb/public/asset/' + video_id), video_id) 460 461 m3u8_url = asset['movie']['contentUrl'] 462 video_id = asset['assetId'] 463 asset_title = asset['title'] 464 title = asset_title['title'] 465 466 formats = self._extract_m3u8_formats( 467 m3u8_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls') 468 self._sort_formats(formats) 469 470 thumbnails = None 471 image_url = asset.get('imageUrl') 472 if image_url: 473 thumbnails = [{ 474 'url': urljoin(url, image_url), 475 'ext': 'jpg', 476 }] 477 478 metadata = asset.get('metadata') or {} 479 480 return { 481 'id': video_id, 482 'title': title, 483 'description': asset_title.get('summaryLong') or asset_title.get('summaryShort'), 484 'thumbnails': thumbnails, 485 'duration': parse_duration(asset_title.get('runTime')), 486 'series': asset.get('tvSeriesTitle'), 487 'season': asset.get('tvSeasonTitle'), 488 'season_number': int_or_none(metadata.get('seasonNumber')), 489 'episode': asset_title.get('titleBrief'), 490 'episode_number': int_or_none(metadata.get('episodeNumber')), 491 'formats': formats, 492 }