mirror of
				https://code.hackerspace.pl/q3k/youtube-dl
				synced 2025-03-16 11:43:02 +00:00 
			
		
		
		
	Merge branch 'master' into HEAD
This commit is contained in:
		
						commit
						51047444aa
					
				| @ -359,6 +359,7 @@ from .nbc import ( | ||||
|     NBCNewsIE, | ||||
|     NBCSportsIE, | ||||
|     NBCSportsVPlayerIE, | ||||
|     MSNBCIE, | ||||
| ) | ||||
| from .ndr import ( | ||||
|     NDRIE, | ||||
| @ -613,7 +614,10 @@ from .testurl import TestURLIE | ||||
| from .testtube import TestTubeIE | ||||
| from .tf1 import TF1IE | ||||
| from .theonion import TheOnionIE | ||||
| from .theplatform import ThePlatformIE | ||||
| from .theplatform import ( | ||||
|     ThePlatformIE, | ||||
|     ThePlatformFeedIE, | ||||
| ) | ||||
| from .thesixtyone import TheSixtyOneIE | ||||
| from .thisamericanlife import ThisAmericanLifeIE | ||||
| from .thisav import ThisAVIE | ||||
|  | ||||
| @ -1052,7 +1052,7 @@ class InfoExtractor(object): | ||||
|         return self._search_regex( | ||||
|             r'(?i)^{([^}]+)?}smil$', smil.tag, 'namespace', default=None) | ||||
| 
 | ||||
|     def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None): | ||||
|     def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None): | ||||
|         base = smil_url | ||||
|         for meta in smil.findall(self._xpath_ns('./head/meta', namespace)): | ||||
|             b = meta.get('base') or meta.get('httpBase') | ||||
| @ -1091,6 +1091,12 @@ class InfoExtractor(object): | ||||
|                     'width': width, | ||||
|                     'height': height, | ||||
|                 }) | ||||
|                 if transform_rtmp_url: | ||||
|                     streamer, src = transform_rtmp_url(streamer, src) | ||||
|                     formats[-1].update({ | ||||
|                         'url': streamer, | ||||
|                         'play_path': src, | ||||
|                     }) | ||||
|                 continue | ||||
| 
 | ||||
|             src_url = src if src.startswith('http') else compat_urlparse.urljoin(base, src) | ||||
|  | ||||
| @ -236,3 +236,28 @@ class NBCNewsIE(InfoExtractor): | ||||
|                 'url': info['videoAssets'][-1]['publicUrl'], | ||||
|                 'ie_key': 'ThePlatform', | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| class MSNBCIE(InfoExtractor): | ||||
|     # https URLs redirect to corresponding http ones | ||||
|     _VALID_URL = r'http://www\.msnbc\.com/[^/]+/watch/(?P<id>[^/]+)' | ||||
|     _TEST = { | ||||
|         'url': 'http://www.msnbc.com/all-in-with-chris-hayes/watch/the-chaotic-gop-immigration-vote-314487875924', | ||||
|         'md5': '6d236bf4f3dddc226633ce6e2c3f814d', | ||||
|         'info_dict': { | ||||
|             'id': 'n_hayes_Aimm_140801_272214', | ||||
|             'ext': 'mp4', | ||||
|             'title': 'The chaotic GOP immigration vote', | ||||
|             'description': 'The Republican House votes on a border bill that has no chance of getting through the Senate or signed by the President and is drawing criticism from all sides.', | ||||
|             'thumbnail': 're:^https?://.*\.jpg$', | ||||
|             'timestamp': 1406937606, | ||||
|             'upload_date': '20140802', | ||||
|             'categories': ['MSNBC/Topics/Franchise/Best of last night', 'MSNBC/Topics/General/Congress'], | ||||
|         }, | ||||
|     } | ||||
| 
 | ||||
|     def _real_extract(self, url): | ||||
|         video_id = self._match_id(url) | ||||
|         webpage = self._download_webpage(url, video_id) | ||||
|         embed_url = self._html_search_meta('embedURL', webpage) | ||||
|         return self.url_result(embed_url) | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import re | ||||
| @ -10,7 +11,8 @@ import hashlib | ||||
| 
 | ||||
| from .common import InfoExtractor | ||||
| from ..compat import ( | ||||
|     compat_str, | ||||
|     compat_parse_qs, | ||||
|     compat_urllib_parse_urlparse, | ||||
| ) | ||||
| from ..utils import ( | ||||
|     determine_ext, | ||||
| @ -18,12 +20,68 @@ from ..utils import ( | ||||
|     xpath_with_ns, | ||||
|     unsmuggle_url, | ||||
|     int_or_none, | ||||
|     url_basename, | ||||
|     float_or_none, | ||||
| ) | ||||
| 
 | ||||
| _x = lambda p: xpath_with_ns(p, {'smil': 'http://www.w3.org/2005/SMIL21/Language'}) | ||||
| default_ns = 'http://www.w3.org/2005/SMIL21/Language' | ||||
| _x = lambda p: xpath_with_ns(p, {'smil': default_ns}) | ||||
| 
 | ||||
| 
 | ||||
| class ThePlatformIE(InfoExtractor): | ||||
| class ThePlatformBaseIE(InfoExtractor): | ||||
|     def _extract_theplatform_smil_formats(self, smil_url, video_id, note='Downloading SMIL data'): | ||||
|         meta = self._download_xml(smil_url, video_id, note=note) | ||||
|         try: | ||||
|             error_msg = next( | ||||
|                 n.attrib['abstract'] | ||||
|                 for n in meta.findall(_x('.//smil:ref')) | ||||
|                 if n.attrib.get('title') == 'Geographic Restriction' or n.attrib.get('title') == 'Expired') | ||||
|         except StopIteration: | ||||
|             pass | ||||
|         else: | ||||
|             raise ExtractorError(error_msg, expected=True) | ||||
| 
 | ||||
|         formats = self._parse_smil_formats( | ||||
|             meta, smil_url, video_id, namespace=default_ns, | ||||
|             # the parameters are from syfy.com, other sites may use others, | ||||
|             # they also work for nbc.com | ||||
|             f4m_params={'g': 'UXWGVKRWHFSP', 'hdcore': '3.0.3'}, | ||||
|             transform_rtmp_url=lambda streamer, src: (streamer, 'mp4:' + src)) | ||||
| 
 | ||||
|         for _format in formats: | ||||
|             ext = determine_ext(_format['url']) | ||||
|             if ext == 'once': | ||||
|                 _format['ext'] = 'mp4' | ||||
| 
 | ||||
|         self._sort_formats(formats) | ||||
| 
 | ||||
|         return formats | ||||
| 
 | ||||
|     def get_metadata(self, path, video_id): | ||||
|         info_url = 'http://link.theplatform.com/s/%s?format=preview' % path | ||||
|         info_json = self._download_webpage(info_url, video_id) | ||||
|         info = json.loads(info_json) | ||||
| 
 | ||||
|         subtitles = {} | ||||
|         captions = info.get('captions') | ||||
|         if isinstance(captions, list): | ||||
|             for caption in captions: | ||||
|                 lang, src, mime = caption.get('lang', 'en'), caption.get('src'), caption.get('type') | ||||
|                 subtitles[lang] = [{ | ||||
|                     'ext': 'srt' if mime == 'text/srt' else 'ttml', | ||||
|                     'url': src, | ||||
|                 }] | ||||
| 
 | ||||
|         return { | ||||
|             'title': info['title'], | ||||
|             'subtitles': subtitles, | ||||
|             'description': info['description'], | ||||
|             'thumbnail': info['defaultThumbnailUrl'], | ||||
|             'duration': int_or_none(info.get('duration'), 1000), | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| class ThePlatformIE(ThePlatformBaseIE): | ||||
|     _VALID_URL = r'''(?x) | ||||
|         (?:https?://(?:link|player)\.theplatform\.com/[sp]/(?P<provider_id>[^/]+)/ | ||||
|            (?:(?P<media>(?:[^/]+/)+select/media/)|(?P<config>(?:[^/\?]+/(?:swf|config)|onsite)/select/))? | ||||
| @ -67,6 +125,20 @@ class ThePlatformIE(InfoExtractor): | ||||
|     }, { | ||||
|         'url': 'http://player.theplatform.com/p/NnzsPC/widget/select/media/4Y0TlYUr_ZT7', | ||||
|         'only_matching': True, | ||||
|     }, { | ||||
|         'url': 'http://player.theplatform.com/p/2E2eJC/nbcNewsOffsite?guid=tdy_or_siri_150701', | ||||
|         'md5': '734f3790fb5fc4903da391beeebc4836', | ||||
|         'info_dict': { | ||||
|             'id': 'tdy_or_siri_150701', | ||||
|             'ext': 'mp4', | ||||
|             'title': 'iPhone Siri’s sassy response to a math question has people talking', | ||||
|             'description': 'md5:a565d1deadd5086f3331d57298ec6333', | ||||
|             'duration': 83.0, | ||||
|             'thumbnail': 're:^https?://.*\.jpg$', | ||||
|             'timestamp': 1435752600, | ||||
|             'upload_date': '20150701', | ||||
|             'categories': ['Today/Shows/Orange Room', 'Today/Sections/Money', 'Today/Topics/Tech', "Today/Topics/Editor's picks"], | ||||
|         }, | ||||
|     }] | ||||
| 
 | ||||
|     @staticmethod | ||||
| @ -101,6 +173,24 @@ class ThePlatformIE(InfoExtractor): | ||||
|             path += '/media' | ||||
|         path += '/' + video_id | ||||
| 
 | ||||
|         qs_dict = compat_parse_qs(compat_urllib_parse_urlparse(url).query) | ||||
|         if 'guid' in qs_dict: | ||||
|             webpage = self._download_webpage(url, video_id) | ||||
|             scripts = re.findall(r'<script[^>]+src="([^"]+)"', webpage) | ||||
|             feed_id = None | ||||
|             # feed id usually locates in the last script. | ||||
|             # Seems there's no pattern for the interested script filename, so | ||||
|             # I try one by one | ||||
|             for script in reversed(scripts): | ||||
|                 feed_script = self._download_webpage(script, video_id, 'Downloading feed script') | ||||
|                 feed_id = self._search_regex(r'defaultFeedId\s*:\s*"([^"]+)"', feed_script, 'default feed id', default=None) | ||||
|                 if feed_id is not None: | ||||
|                     break | ||||
|             if feed_id is None: | ||||
|                 raise ExtractorError('Unable to find feed id') | ||||
|             return self.url_result('http://feed.theplatform.com/f/%s/%s?byGuid=%s' % ( | ||||
|                 provider_id, feed_id, qs_dict['guid'][0])) | ||||
| 
 | ||||
|         if smuggled_data.get('force_smil_url', False): | ||||
|             smil_url = url | ||||
|         elif mobj.group('config'): | ||||
| @ -120,95 +210,78 @@ class ThePlatformIE(InfoExtractor): | ||||
|         if sig: | ||||
|             smil_url = self._sign_url(smil_url, sig['key'], sig['secret']) | ||||
| 
 | ||||
|         meta = self._download_xml(smil_url, video_id) | ||||
|         try: | ||||
|             error_msg = next( | ||||
|                 n.attrib['abstract'] | ||||
|                 for n in meta.findall(_x('.//smil:ref')) | ||||
|                 if n.attrib.get('title') == 'Geographic Restriction' or n.attrib.get('title') == 'Expired') | ||||
|         except StopIteration: | ||||
|             pass | ||||
|         else: | ||||
|             raise ExtractorError(error_msg, expected=True) | ||||
|         formats = self._extract_theplatform_smil_formats(smil_url, video_id) | ||||
| 
 | ||||
|         info_url = 'http://link.theplatform.com/s/%s?format=preview' % path | ||||
|         info_json = self._download_webpage(info_url, video_id) | ||||
|         info = json.loads(info_json) | ||||
| 
 | ||||
|         subtitles = {} | ||||
|         captions = info.get('captions') | ||||
|         if isinstance(captions, list): | ||||
|             for caption in captions: | ||||
|                 lang, src, mime = caption.get('lang', 'en'), caption.get('src'), caption.get('type') | ||||
|                 subtitles[lang] = [{ | ||||
|                     'ext': 'srt' if mime == 'text/srt' else 'ttml', | ||||
|                     'url': src, | ||||
|                 }] | ||||
| 
 | ||||
|         head = meta.find(_x('smil:head')) | ||||
|         body = meta.find(_x('smil:body')) | ||||
| 
 | ||||
|         f4m_node = body.find(_x('smil:seq//smil:video')) | ||||
|         if f4m_node is None: | ||||
|             f4m_node = body.find(_x('smil:seq/smil:video')) | ||||
|         if f4m_node is not None and '.f4m' in f4m_node.attrib['src']: | ||||
|             f4m_url = f4m_node.attrib['src'] | ||||
|             if 'manifest.f4m?' not in f4m_url: | ||||
|                 f4m_url += '?' | ||||
|             # the parameters are from syfy.com, other sites may use others, | ||||
|             # they also work for nbc.com | ||||
|             f4m_url += '&g=UXWGVKRWHFSP&hdcore=3.0.3' | ||||
|             formats = self._extract_f4m_formats(f4m_url, video_id) | ||||
|         else: | ||||
|             formats = [] | ||||
|             switch = body.find(_x('smil:switch')) | ||||
|             if switch is None: | ||||
|                 switch = body.find(_x('smil:par//smil:switch')) | ||||
|             if switch is None: | ||||
|                 switch = body.find(_x('smil:par/smil:switch')) | ||||
|             if switch is None: | ||||
|                 switch = body.find(_x('smil:par')) | ||||
|             if switch is not None: | ||||
|                 base_url = head.find(_x('smil:meta')).attrib['base'] | ||||
|                 for f in switch.findall(_x('smil:video')): | ||||
|                     attr = f.attrib | ||||
|                     width = int_or_none(attr.get('width')) | ||||
|                     height = int_or_none(attr.get('height')) | ||||
|                     vbr = int_or_none(attr.get('system-bitrate'), 1000) | ||||
|                     format_id = '%dx%d_%dk' % (width, height, vbr) | ||||
|                     formats.append({ | ||||
|                         'format_id': format_id, | ||||
|                         'url': base_url, | ||||
|                         'play_path': 'mp4:' + attr['src'], | ||||
|                         'ext': 'flv', | ||||
|                         'width': width, | ||||
|                         'height': height, | ||||
|                         'vbr': vbr, | ||||
|                     }) | ||||
|             else: | ||||
|                 switch = body.find(_x('smil:seq//smil:switch')) | ||||
|                 if switch is None: | ||||
|                     switch = body.find(_x('smil:seq/smil:switch')) | ||||
|                 for f in switch.findall(_x('smil:video')): | ||||
|                     attr = f.attrib | ||||
|                     vbr = int_or_none(attr.get('system-bitrate'), 1000) | ||||
|                     ext = determine_ext(attr['src']) | ||||
|                     if ext == 'once': | ||||
|                         ext = 'mp4' | ||||
|                     formats.append({ | ||||
|                         'format_id': compat_str(vbr), | ||||
|                         'url': attr['src'], | ||||
|                         'vbr': vbr, | ||||
|                         'ext': ext, | ||||
|                     }) | ||||
|             self._sort_formats(formats) | ||||
| 
 | ||||
|         return { | ||||
|         ret = self.get_metadata(path, video_id) | ||||
|         ret.update({ | ||||
|             'id': video_id, | ||||
|             'title': info['title'], | ||||
|             'subtitles': subtitles, | ||||
|             'formats': formats, | ||||
|             'description': info['description'], | ||||
|             'thumbnail': info['defaultThumbnailUrl'], | ||||
|             'duration': int_or_none(info.get('duration'), 1000), | ||||
|         } | ||||
|         }) | ||||
| 
 | ||||
|         return ret | ||||
| 
 | ||||
| 
 | ||||
| class ThePlatformFeedIE(ThePlatformBaseIE): | ||||
|     _URL_TEMPLATE = '%s//feed.theplatform.com/f/%s/%s?form=json&byGuid=%s' | ||||
|     _VALID_URL = r'https?://feed\.theplatform\.com/f/(?P<provider_id>[^/]+)/(?P<feed_id>[^?/]+)\?(?:[^&]+&)*byGuid=(?P<id>[a-zA-Z0-9_]+)' | ||||
|     _TEST = { | ||||
|         # From http://player.theplatform.com/p/7wvmTC/MSNBCEmbeddedOffSite?guid=n_hardball_5biden_140207 | ||||
|         'url': 'http://feed.theplatform.com/f/7wvmTC/msnbc_video-p-test?form=json&pretty=true&range=-40&byGuid=n_hardball_5biden_140207', | ||||
|         'md5': '22d2b84f058d3586efcd99e57d59d314', | ||||
|         'info_dict': { | ||||
|             'id': 'n_hardball_5biden_140207', | ||||
|             'ext': 'mp4', | ||||
|             'title': 'The Biden factor: will Joe run in 2016?', | ||||
|             'description': 'Could Vice President Joe Biden be preparing a 2016 campaign? Mark Halperin and Sam Stein weigh in.', | ||||
|             'thumbnail': 're:^https?://.*\.jpg$', | ||||
|             'upload_date': '20140208', | ||||
|             'timestamp': 1391824260, | ||||
|             'duration': 467.0, | ||||
|             'categories': ['MSNBC/Issues/Democrats', 'MSNBC/Issues/Elections/Election 2016'], | ||||
|         }, | ||||
|     } | ||||
| 
 | ||||
|     def _real_extract(self, url): | ||||
|         mobj = re.match(self._VALID_URL, url) | ||||
| 
 | ||||
|         video_id = mobj.group('id') | ||||
|         provider_id = mobj.group('provider_id') | ||||
|         feed_id = mobj.group('feed_id') | ||||
| 
 | ||||
|         real_url = self._URL_TEMPLATE % (self.http_scheme(), provider_id, feed_id, video_id) | ||||
|         feed = self._download_json(real_url, video_id) | ||||
|         entry = feed['entries'][0] | ||||
| 
 | ||||
|         formats = [] | ||||
|         first_video_id = None | ||||
|         duration = None | ||||
|         for item in entry['media$content']: | ||||
|             smil_url = item['plfile$url'] + '&format=SMIL&Tracking=true&Embedded=true&formats=MPEG4,F4M' | ||||
|             cur_video_id = url_basename(smil_url) | ||||
|             if first_video_id is None: | ||||
|                 first_video_id = cur_video_id | ||||
|                 duration = float_or_none(item.get('plfile$duration')) | ||||
|             formats.extend(self._extract_theplatform_smil_formats(smil_url, video_id, 'Downloading SMIL data for %s' % cur_video_id)) | ||||
| 
 | ||||
|         self._sort_formats(formats) | ||||
| 
 | ||||
|         thumbnails = [{ | ||||
|             'url': thumbnail['plfile$url'], | ||||
|             'width': int_or_none(thumbnail.get('plfile$width')), | ||||
|             'height': int_or_none(thumbnail.get('plfile$height')), | ||||
|         } for thumbnail in entry.get('media$thumbnails', [])] | ||||
| 
 | ||||
|         timestamp = int_or_none(entry.get('media$availableDate'), scale=1000) | ||||
|         categories = [item['media$name'] for item in entry.get('media$categories', [])] | ||||
| 
 | ||||
|         ret = self.get_metadata('%s/%s' % (provider_id, first_video_id), video_id) | ||||
|         ret.update({ | ||||
|             'id': video_id, | ||||
|             'formats': formats, | ||||
|             'thumbnails': thumbnails, | ||||
|             'duration': duration, | ||||
|             'timestamp': timestamp, | ||||
|             'categories': categories, | ||||
|         }) | ||||
| 
 | ||||
|         return ret | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Yen Chi Hsuan
						Yen Chi Hsuan