diff options
Diffstat (limited to 'youtube_dl/InfoExtractors.py')
| -rw-r--r-- | youtube_dl/InfoExtractors.py | 754 | 
1 files changed, 666 insertions, 88 deletions
| diff --git a/youtube_dl/InfoExtractors.py b/youtube_dl/InfoExtractors.py index baf859ea8..13b04ab5b 100644 --- a/youtube_dl/InfoExtractors.py +++ b/youtube_dl/InfoExtractors.py @@ -13,6 +13,8 @@ import urllib  import urllib2  import email.utils  import xml.etree.ElementTree +import random +import math  from urlparse import parse_qs  try: @@ -95,7 +97,26 @@ class InfoExtractor(object):  class YoutubeIE(InfoExtractor):  	"""Information extractor for youtube.com.""" -	_VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$' +	_VALID_URL = r"""^ +	                 ( +	                     (?:https?://)?                                       # http(s):// (optional) +	                     (?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/| +	                     	tube\.majestyc\.net/)                             # the various hostnames, with wildcard subdomains +	                     (?:.*?\#/)?                                          # handle anchor (#/) redirect urls +	                     (?!view_play_list|my_playlists|artist|playlist)      # ignore playlist URLs +	                     (?:                                                  # the various things that can precede the ID: +	                         (?:(?:v|embed|e)/)                               # v/ or embed/ or e/ +	                         |(?:                                             # or the v= param in all its forms +	                             (?:watch(?:_popup)?(?:\.php)?)?              # preceding watch(_popup|.php) or nothing (like /?v=xxxx) +	                             (?:\?|\#!?)                                  # the params delimiter ? or # or #! +	                             (?:.+&)?                                     # any other preceding param (like /?s=tuff&v=xxxx) +	                             v= +	                         ) +	                     )?                                                   # optional -> youtube.com/xxxx is OK +	                 )?                                                       # all until now is optional -> you can pass the naked ID +	                 ([0-9A-Za-z_-]+)                                         # here is it! the YouTube video ID +	                 (?(1).+)?                                                # if we found the ID, everything can follow +	                 $"""  	_LANG_URL = r'http://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'  	_LOGIN_URL = 'https://www.youtube.com/signup?next=/&gl=US&hl=en'  	_AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en' @@ -134,6 +155,10 @@ class YoutubeIE(InfoExtractor):  	}	  	IE_NAME = u'youtube' +	def suitable(self, url): +		"""Receives a URL and returns True if suitable for this IE.""" +		return re.match(self._VALID_URL, url, re.VERBOSE) is not None +  	def report_lang(self):  		"""Report attempt to set language."""  		self._downloader.to_screen(u'[youtube] Setting language') @@ -188,9 +213,9 @@ class YoutubeIE(InfoExtractor):  		return srt  	def _print_formats(self, formats): -		print 'Available formats:' +		print('Available formats:')  		for x in formats: -			print '%s\t:\t%s\t[%s]' %(x, self._video_extensions.get(x, 'flv'), self._video_dimensions.get(x, '???')) +			print('%s\t:\t%s\t[%s]' %(x, self._video_extensions.get(x, 'flv'), self._video_dimensions.get(x, '???')))  	def _real_initialize(self):  		if self._downloader is None: @@ -213,7 +238,7 @@ class YoutubeIE(InfoExtractor):  				else:  					raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)  			except (IOError, netrc.NetrcParseError), err: -				self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err)) +				self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % compat_str(err))  				return  		# Set language @@ -222,7 +247,7 @@ class YoutubeIE(InfoExtractor):  			self.report_lang()  			urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.to_stderr(u'WARNING: unable to set language: %s' % str(err)) +			self._downloader.to_stderr(u'WARNING: unable to set language: %s' % compat_str(err))  			return  		# No authentication to be performed @@ -245,7 +270,7 @@ class YoutubeIE(InfoExtractor):  				self._downloader.to_stderr(u'WARNING: unable to log in: bad username or password')  				return  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.to_stderr(u'WARNING: unable to log in: %s' % str(err)) +			self._downloader.to_stderr(u'WARNING: unable to log in: %s' % compat_str(err))  			return  		# Confirm age @@ -258,7 +283,7 @@ class YoutubeIE(InfoExtractor):  			self.report_age_confirmation()  			age_results = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to confirm age: %s' % compat_str(err))  			return  	def _real_extract(self, url): @@ -268,7 +293,7 @@ class YoutubeIE(InfoExtractor):  			url = 'http://www.youtube.com/' + urllib.unquote(mobj.group(1)).lstrip('/')  		# Extract video id from URL -		mobj = re.match(self._VALID_URL, url) +		mobj = re.match(self._VALID_URL, url, re.VERBOSE)  		if mobj is None:  			self._downloader.trouble(u'ERROR: invalid URL: %s' % url)  			return @@ -280,7 +305,7 @@ class YoutubeIE(InfoExtractor):  		try:  			video_webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		# Attempt to extract SWF player URL @@ -302,7 +327,7 @@ class YoutubeIE(InfoExtractor):  				if 'token' in video_info:  					break  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % compat_str(err))  				return  		if 'token' not in video_info:  			if 'reason' in video_info: @@ -365,8 +390,9 @@ class YoutubeIE(InfoExtractor):  				try:  					srt_list = urllib2.urlopen(request).read()  				except (urllib2.URLError, httplib.HTTPException, socket.error), err: -					raise Trouble(u'WARNING: unable to download video subtitles: %s' % str(err)) -				srt_lang_list = re.findall(r'lang_code="([\w\-]+)"', srt_list) +					raise Trouble(u'WARNING: unable to download video subtitles: %s' % compat_str(err)) +				srt_lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', srt_list) +				srt_lang_list = dict((l[1], l[0]) for l in srt_lang_list)  				if not srt_lang_list:  					raise Trouble(u'WARNING: video has no closed captions')  				if self._downloader.params.get('subtitleslang', False): @@ -374,18 +400,26 @@ class YoutubeIE(InfoExtractor):  				elif 'en' in srt_lang_list:  					srt_lang = 'en'  				else: -					srt_lang = srt_lang_list[0] +					srt_lang = srt_lang_list.keys()[0]  				if not srt_lang in srt_lang_list:  					raise Trouble(u'WARNING: no closed captions found in the specified language') -				request = urllib2.Request('http://video.google.com/timedtext?hl=en&lang=%s&v=%s' % (srt_lang, video_id)) +				request = urllib2.Request('http://www.youtube.com/api/timedtext?lang=%s&name=%s&v=%s' % (srt_lang, srt_lang_list[srt_lang], video_id))  				try:  					srt_xml = urllib2.urlopen(request).read()  				except (urllib2.URLError, httplib.HTTPException, socket.error), err: -					raise Trouble(u'WARNING: unable to download video subtitles: %s' % str(err)) +					raise Trouble(u'WARNING: unable to download video subtitles: %s' % compat_str(err)) +				if not srt_xml: +					raise Trouble(u'WARNING: unable to download video subtitles')  				video_subtitles = self._closed_captions_xml_to_srt(srt_xml.decode('utf-8'))  			except Trouble as trouble:  				self._downloader.trouble(trouble[0]) +		if 'length_seconds' not in video_info: +			self._downloader.trouble(u'WARNING: unable to extract video duration') +			video_duration = '' +		else: +			video_duration = urllib.unquote_plus(video_info['length_seconds'][0]) +  		# token  		video_token = urllib.unquote_plus(video_info['token'][0]) @@ -399,7 +433,7 @@ class YoutubeIE(InfoExtractor):  			url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',')  			url_data = [parse_qs(uds) for uds in url_data_strs]  			url_data = filter(lambda ud: 'itag' in ud and 'url' in ud, url_data) -			url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data) +			url_map = dict((ud['itag'][0], ud['url'][0] + '&signature=' + ud['sig'][0]) for ud in url_data)  			format_limit = self._downloader.params.get('format_limit', None)  			available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats @@ -452,7 +486,8 @@ class YoutubeIE(InfoExtractor):  				'thumbnail':	video_thumbnail.decode('utf-8'),  				'description':	video_description,  				'player_url':	player_url, -				'subtitles':	video_subtitles +				'subtitles':	video_subtitles, +				'duration':		video_duration  			})  		return results @@ -491,7 +526,7 @@ class MetacafeIE(InfoExtractor):  			self.report_disclaimer()  			disclaimer = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to retrieve disclaimer: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to retrieve disclaimer: %s' % compat_str(err))  			return  		# Confirm age @@ -504,7 +539,7 @@ class MetacafeIE(InfoExtractor):  			self.report_age_confirmation()  			disclaimer = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to confirm age: %s' % compat_str(err))  			return  	def _real_extract(self, url): @@ -528,7 +563,7 @@ class MetacafeIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % compat_str(err))  			return  		# Extract URL, uploader and title from webpage @@ -568,7 +603,7 @@ class MetacafeIE(InfoExtractor):  			return  		video_title = mobj.group(1).decode('utf-8') -		mobj = re.search(r'(?ms)By:\s*<a .*?>(.+?)<', webpage) +		mobj = re.search(r'submitter=(.*?);', webpage)  		if mobj is None:  			self._downloader.trouble(u'ERROR: unable to extract uploader nickname')  			return @@ -589,7 +624,7 @@ class MetacafeIE(InfoExtractor):  class DailymotionIE(InfoExtractor):  	"""Information Extractor for Dailymotion""" -	_VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)' +	_VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)'  	IE_NAME = u'dailymotion'  	def __init__(self, downloader=None): @@ -610,9 +645,9 @@ class DailymotionIE(InfoExtractor):  			self._downloader.trouble(u'ERROR: invalid URL: %s' % url)  			return -		video_id = mobj.group(1) +		video_id = mobj.group(1).split('_')[0].split('?')[0] -		video_extension = 'flv' +		video_extension = 'mp4'  		# Retrieve video webpage to extract further information  		request = urllib2.Request(url) @@ -621,25 +656,34 @@ class DailymotionIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % compat_str(err))  			return  		# Extract URL, uploader and title from webpage  		self.report_extraction(video_id) -		mobj = re.search(r'(?i)addVariable\(\"sequence\"\s*,\s*\"([^\"]+?)\"\)', webpage) +		mobj = re.search(r'\s*var flashvars = (.*)', webpage)  		if mobj is None:  			self._downloader.trouble(u'ERROR: unable to extract media URL')  			return -		sequence = urllib.unquote(mobj.group(1)) -		mobj = re.search(r',\"sdURL\"\:\"([^\"]+?)\",', sequence) +		flashvars = urllib.unquote(mobj.group(1)) + +		for key in ['hd1080URL', 'hd720URL', 'hqURL', 'sdURL', 'ldURL', 'video_url']: +			if key in flashvars: +				max_quality = key +				self._downloader.to_screen(u'[dailymotion] Using %s' % key) +				break +		else: +			self._downloader.trouble(u'ERROR: unable to extract video URL') +			return + +		mobj = re.search(r'"' + max_quality + r'":"(.+?)"', flashvars)  		if mobj is None: -			self._downloader.trouble(u'ERROR: unable to extract media URL') +			self._downloader.trouble(u'ERROR: unable to extract video URL')  			return -		mediaURL = urllib.unquote(mobj.group(1)).replace('\\', '') -		# if needed add http://www.dailymotion.com/ if relative URL +		video_url = urllib.unquote(mobj.group(1)).replace('\\/', '/') -		video_url = mediaURL +		# TODO: support choosing qualities  		mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)  		if mobj is None: @@ -647,17 +691,28 @@ class DailymotionIE(InfoExtractor):  			return  		video_title = unescapeHTML(mobj.group('title').decode('utf-8')) -		mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a></span>', webpage) +		video_uploader = u'NA' +		mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a>', webpage)  		if mobj is None: -			self._downloader.trouble(u'ERROR: unable to extract uploader nickname') -			return -		video_uploader = mobj.group(1) +			# lookin for official user +			mobj_official = re.search(r'<span rel="author"[^>]+?>([^<]+?)</span>', webpage) +			if mobj_official is None: +				self._downloader.trouble(u'WARNING: unable to extract uploader nickname') +			else: +				video_uploader = mobj_official.group(1) +		else: +			video_uploader = mobj.group(1) + +		video_upload_date = u'NA' +		mobj = re.search(r'<div class="[^"]*uploaded_cont[^"]*" title="[^"]*">([0-9]{2})-([0-9]{2})-([0-9]{4})</div>', webpage) +		if mobj is not None: +			video_upload_date = mobj.group(3) + mobj.group(2) + mobj.group(1)  		return [{  			'id':		video_id.decode('utf-8'),  			'url':		video_url.decode('utf-8'),  			'uploader':	video_uploader.decode('utf-8'), -			'upload_date':	u'NA', +			'upload_date':	video_upload_date,  			'title':	video_title,  			'ext':		video_extension.decode('utf-8'),  			'format':	u'NA', @@ -699,7 +754,7 @@ class GoogleIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		# Extract URL, uploader, and title from webpage @@ -738,7 +793,7 @@ class GoogleIE(InfoExtractor):  			try:  				webpage = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  				return  			mobj = re.search(r'<img class=thumbnail-img (?:.* )?src=(http.*)>', webpage)  			if mobj is None: @@ -794,7 +849,7 @@ class PhotobucketIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		# Extract URL, uploader, and title from webpage @@ -864,7 +919,7 @@ class YahooIE(InfoExtractor):  			try:  				webpage = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  				return  			mobj = re.search(r'\("id", "([0-9]+)"\);', webpage) @@ -888,7 +943,7 @@ class YahooIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		# Extract uploader and title from webpage @@ -946,7 +1001,7 @@ class YahooIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		# Extract media URL from playlist XML @@ -975,7 +1030,7 @@ class VimeoIE(InfoExtractor):  	"""Information extractor for vimeo.com."""  	# _VALID_URL matches Vimeo URLs -	_VALID_URL = r'(?:https?://)?(?:(?:www|player).)?vimeo\.com/(?:groups/[^/]+/)?(?:videos?/)?([0-9]+)' +	_VALID_URL = r'(?:https?://)?(?:(?:www|player).)?vimeo\.com/(?:(?:groups|album)/[^/]+/)?(?:videos?/)?([0-9]+)'  	IE_NAME = u'vimeo'  	def __init__(self, downloader=None): @@ -1004,7 +1059,7 @@ class VimeoIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		# Now we begin extracting as much information as we can from what we @@ -1045,21 +1100,32 @@ class VimeoIE(InfoExtractor):  		timestamp = config['request']['timestamp']  		# Vimeo specific: extract video codec and quality information +		# First consider quality, then codecs, then take everything  		# TODO bind to format param  		codecs = [('h264', 'mp4'), ('vp8', 'flv'), ('vp6', 'flv')] -		for codec in codecs: -			if codec[0] in config["video"]["files"]: -				video_codec = codec[0] -				video_extension = codec[1] -				if 'hd' in config["video"]["files"][codec[0]]: quality = 'hd' -				else: quality = 'sd' +		files = { 'hd': [], 'sd': [], 'other': []} +		for codec_name, codec_extension in codecs: +			if codec_name in config["video"]["files"]: +				if 'hd' in config["video"]["files"][codec_name]: +					files['hd'].append((codec_name, codec_extension, 'hd')) +				elif 'sd' in config["video"]["files"][codec_name]: +					files['sd'].append((codec_name, codec_extension, 'sd')) +				else: +					files['other'].append((codec_name, codec_extension, config["video"]["files"][codec_name][0])) + +		for quality in ('hd', 'sd', 'other'): +			if len(files[quality]) > 0: +				video_quality = files[quality][0][2] +				video_codec = files[quality][0][0] +				video_extension = files[quality][0][1] +				self._downloader.to_screen(u'[vimeo] %s: Downloading %s file at %s quality' % (video_id, video_codec.upper(), video_quality))  				break  		else:  			self._downloader.trouble(u'ERROR: no known codec found')  			return  		video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \ -					%(video_id, sig, timestamp, quality, video_codec.upper()) +					%(video_id, sig, timestamp, video_quality, video_codec.upper())  		return [{  			'id':		video_id, @@ -1159,7 +1225,7 @@ class GenericIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		except ValueError, err:  			# since this is the last-resort InfoExtractor, if @@ -1280,7 +1346,7 @@ class YoutubeSearchIE(InfoExtractor):  			try:  				data = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download API page: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download API page: %s' % compat_str(err))  				return  			api_response = json.loads(data)['data'] @@ -1357,7 +1423,7 @@ class GoogleSearchIE(InfoExtractor):  			try:  				page = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err))  				return  			# Extract video identifiers @@ -1440,7 +1506,7 @@ class YahooSearchIE(InfoExtractor):  			try:  				page = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err))  				return  			# Extract video identifiers @@ -1466,9 +1532,9 @@ class YahooSearchIE(InfoExtractor):  class YoutubePlaylistIE(InfoExtractor):  	"""Information Extractor for YouTube playlists.""" -	_VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*' +	_VALID_URL = r'(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL|EC)?|PL|EC)([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'  	_TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en' -	_VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&list=(PL)?%s&' +	_VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&([^&"]+&)*list=.*?%s'  	_MORE_PAGES_INDICATOR = r'yt-uix-pager-next'  	IE_NAME = u'youtube:playlist' @@ -1510,7 +1576,7 @@ class YoutubePlaylistIE(InfoExtractor):  			try:  				page = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err))  				return  			# Extract video identifiers @@ -1536,6 +1602,56 @@ class YoutubePlaylistIE(InfoExtractor):  		return +class YoutubeChannelIE(InfoExtractor): +	"""Information Extractor for YouTube channels.""" + +	_VALID_URL = r"^(?:https?://)?(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/([0-9A-Za-z_-]+)(?:/.*)?$" +	_TEMPLATE_URL = 'http://www.youtube.com/channel/%s/videos?sort=da&flow=list&view=0&page=%s&gl=US&hl=en' +	_MORE_PAGES_INDICATOR = r'yt-uix-button-content">Next' # TODO +	IE_NAME = u'youtube:channel' + +	def report_download_page(self, channel_id, pagenum): +		"""Report attempt to download channel page with given number.""" +		self._downloader.to_screen(u'[youtube] Channel %s: Downloading page #%s' % (channel_id, pagenum)) + +	def _real_extract(self, url): +		# Extract channel id +		mobj = re.match(self._VALID_URL, url) +		if mobj is None: +			self._downloader.trouble(u'ERROR: invalid url: %s' % url) +			return + +		# Download channel pages +		channel_id = mobj.group(1) +		video_ids = [] +		pagenum = 1 + +		while True: +			self.report_download_page(channel_id, pagenum) +			url = self._TEMPLATE_URL % (channel_id, pagenum) +			request = urllib2.Request(url) +			try: +				page = urllib2.urlopen(request).read() +			except (urllib2.URLError, httplib.HTTPException, socket.error), err: +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err)) +				return + +			# Extract video identifiers +			ids_in_page = [] +			for mobj in re.finditer(r'href="/watch\?v=([0-9A-Za-z_-]+)&', page): +				if mobj.group(1) not in ids_in_page: +					ids_in_page.append(mobj.group(1)) +			video_ids.extend(ids_in_page) + +			if re.search(self._MORE_PAGES_INDICATOR, page) is None: +				break +			pagenum = pagenum + 1 + +		for id in video_ids: +			self._downloader.download(['http://www.youtube.com/watch?v=%s' % id]) +		return + +  class YoutubeUserIE(InfoExtractor):  	"""Information Extractor for YouTube users.""" @@ -1580,7 +1696,7 @@ class YoutubeUserIE(InfoExtractor):  			try:  				page = urllib2.urlopen(request).read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err))  				return  			# Extract video identifiers @@ -1619,6 +1735,98 @@ class YoutubeUserIE(InfoExtractor):  			self._downloader.download(['http://www.youtube.com/watch?v=%s' % video_id]) +class BlipTVUserIE(InfoExtractor): +	"""Information Extractor for blip.tv users.""" + +	_VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?blip\.tv/)|bliptvuser:)([^/]+)/*$' +	_PAGE_SIZE = 12 +	IE_NAME = u'blip.tv:user' + +	def __init__(self, downloader=None): +		InfoExtractor.__init__(self, downloader) + +	def report_download_page(self, username, pagenum): +		"""Report attempt to download user page.""" +		self._downloader.to_screen(u'[%s] user %s: Downloading video ids from page %d' % +				(self.IE_NAME, username, pagenum)) + +	def _real_extract(self, url): +		# Extract username +		mobj = re.match(self._VALID_URL, url) +		if mobj is None: +			self._downloader.trouble(u'ERROR: invalid url: %s' % url) +			return + +		username = mobj.group(1) + +		page_base = 'http://m.blip.tv/pr/show_get_full_episode_list?users_id=%s&lite=0&esi=1' + +		request = urllib2.Request(url) + +		try: +			page = urllib2.urlopen(request).read().decode('utf-8') +			mobj = re.search(r'data-users-id="([^"]+)"', page) +			page_base = page_base % mobj.group(1) +		except (urllib2.URLError, httplib.HTTPException, socket.error), err: +			self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err)) +			return + + +		# Download video ids using BlipTV Ajax calls. Result size per +		# query is limited (currently to 12 videos) so we need to query +		# page by page until there are no video ids - it means we got +		# all of them. + +		video_ids = [] +		pagenum = 1 + +		while True: +			self.report_download_page(username, pagenum) + +			request = urllib2.Request( page_base + "&page=" + str(pagenum) ) + +			try: +				page = urllib2.urlopen(request).read().decode('utf-8') +			except (urllib2.URLError, httplib.HTTPException, socket.error), err: +				self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err)) +				return + +			# Extract video identifiers +			ids_in_page = [] + +			for mobj in re.finditer(r'href="/([^"]+)"', page): +				if mobj.group(1) not in ids_in_page: +					ids_in_page.append(unescapeHTML(mobj.group(1))) + +			video_ids.extend(ids_in_page) + +			# A little optimization - if current page is not +			# "full", ie. does not contain PAGE_SIZE video ids then +			# we can assume that this page is the last one - there +			# are no more ids on further pages - no need to query +			# again. + +			if len(ids_in_page) < self._PAGE_SIZE: +				break + +			pagenum += 1 + +		all_ids_count = len(video_ids) +		playliststart = self._downloader.params.get('playliststart', 1) - 1 +		playlistend = self._downloader.params.get('playlistend', -1) + +		if playlistend == -1: +			video_ids = video_ids[playliststart:] +		else: +			video_ids = video_ids[playliststart:playlistend] + +		self._downloader.to_screen(u"[%s] user %s: Collected %d video ids (downloading %d of them)" % +				(self.IE_NAME, username, all_ids_count, len(video_ids))) + +		for video_id in video_ids: +			self._downloader.download([u'http://blip.tv/'+video_id]) + +  class DepositFilesIE(InfoExtractor):  	"""Information extractor for depositfiles.com""" @@ -1648,7 +1856,7 @@ class DepositFilesIE(InfoExtractor):  			self.report_download_webpage(file_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve file webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve file webpage: %s' % compat_str(err))  			return  		# Search for the real file URL @@ -1765,7 +1973,7 @@ class FacebookIE(InfoExtractor):  				else:  					raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)  			except (IOError, netrc.NetrcParseError), err: -				self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err)) +				self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % compat_str(err))  				return  		if useremail is None: @@ -1785,7 +1993,7 @@ class FacebookIE(InfoExtractor):  				self._downloader.to_stderr(u'WARNING: unable to log in: bad username/password, or exceded login rate limit (~3/min). Check credentials or wait.')  				return  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.to_stderr(u'WARNING: unable to log in: %s' % str(err)) +			self._downloader.to_stderr(u'WARNING: unable to log in: %s' % compat_str(err))  			return  	def _real_extract(self, url): @@ -1802,7 +2010,7 @@ class FacebookIE(InfoExtractor):  			page = urllib2.urlopen(request)  			video_webpage = page.read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		# Start extracting information @@ -1917,7 +2125,7 @@ class BlipTVIE(InfoExtractor):  		else:  			cchar = '?'  		json_url = url + cchar + 'skin=json&version=2&no_wrap=1' -		request = urllib2.Request(json_url) +		request = urllib2.Request(json_url.encode('utf-8'))  		self.report_extraction(mobj.group(1))  		info = None  		try: @@ -1936,13 +2144,13 @@ class BlipTVIE(InfoExtractor):  					'urlhandle': urlh  				}  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % compat_str(err))  			return  		if info is None: # Regular URL  			try:  				json_code = urlh.read()  			except (urllib2.URLError, httplib.HTTPException, socket.error), err: -				self._downloader.trouble(u'ERROR: unable to read video info webpage: %s' % str(err)) +				self._downloader.trouble(u'ERROR: unable to read video info webpage: %s' % compat_str(err))  				return  			try: @@ -1975,6 +2183,7 @@ class BlipTVIE(InfoExtractor):  				self._downloader.trouble(u'ERROR: unable to parse video information: %s' % repr(err))  				return +		std_headers['User-Agent'] = 'iTunes/10.6.1'  		return [info] @@ -2009,7 +2218,7 @@ class MyVideoIE(InfoExtractor):  			self.report_download_webpage(video_id)  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err))  			return  		self.report_extraction(video_id) @@ -2044,6 +2253,25 @@ class ComedyCentralIE(InfoExtractor):  	_VALID_URL = r'^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport))|(https?://)?(www\.)?(?P<showname>thedailyshow|colbertnation)\.com/full-episodes/(?P<episode>.*)$'  	IE_NAME = u'comedycentral' +	_available_formats = ['3500', '2200', '1700', '1200', '750', '400'] + +	_video_extensions = { +		'3500': 'mp4', +		'2200': 'mp4', +		'1700': 'mp4', +		'1200': 'mp4', +		'750': 'mp4', +		'400': 'mp4', +	} +	_video_dimensions = { +		'3500': '1280x720', +		'2200': '960x540', +		'1700': '768x432', +		'1200': '640x360', +		'750': '512x288', +		'400': '384x216', +	} +  	def report_extraction(self, episode_id):  		self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id) @@ -2056,6 +2284,13 @@ class ComedyCentralIE(InfoExtractor):  	def report_player_url(self, episode_id):  		self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_id) + +	def _print_formats(self, formats): +		print('Available formats:') +		for x in formats: +			print('%s\t:\t%s\t[%s]' %(x, self._video_extensions.get(x, 'mp4'), self._video_dimensions.get(x, '???'))) + +  	def _real_extract(self, url):  		mobj = re.match(self._VALID_URL, url)  		if mobj is None: @@ -2096,10 +2331,19 @@ class ComedyCentralIE(InfoExtractor):  			epTitle = mobj.group('episode')  		mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*episode.*?:.*?))"', html) +  		if len(mMovieParams) == 0: -			self._downloader.trouble(u'ERROR: unable to find Flash URL in webpage ' + url) -			return +			# The Colbert Report embeds the information in a without +			# a URL prefix; so extract the alternate reference +			# and then add the URL prefix manually. +			altMovieParams = re.findall('data-mgid="([^"]*episode.*?:.*?)"', html) +			if len(altMovieParams) == 0: +				self._downloader.trouble(u'ERROR: unable to find Flash URL in webpage ' + url) +				return +			else: +				mMovieParams = [("http://media.mtvnservices.com/" + altMovieParams[0], altMovieParams[0])] +		  		playerUrl_raw = mMovieParams[0][0]  		self.report_player_url(epTitle)  		try: @@ -2148,10 +2392,31 @@ class ComedyCentralIE(InfoExtractor):  			if len(turls) == 0:  				self._downloader.trouble(u'\nERROR: unable to download ' + mediaId + ': No videos found')  				continue +			 +			if self._downloader.params.get('listformats', None): +				self._print_formats([i[0] for i in turls]) +				return  			# For now, just pick the highest bitrate  			format,video_url = turls[-1] +			# Get the format arg from the arg stream +			req_format = self._downloader.params.get('format', None) + +			# Select format if we can find one +			for f,v in turls: +				if f == req_format: +					format, video_url = f, v +					break + +			# Patch to download from alternative CDN, which does not +			# break on current RTMPDump builds +			broken_cdn = "rtmpe://viacomccstrmfs.fplive.net/viacomccstrm/gsp.comedystor/" +			better_cdn = "rtmpe://cp10740.edgefcs.net/ondemand/mtvnorigin/gsp.comedystor/" + +			if video_url.startswith(broken_cdn): +				video_url = video_url.replace(broken_cdn, better_cdn) +  			effTitle = showId + u'-' + epTitle  			info = {  				'id': shortMediaId, @@ -2163,7 +2428,7 @@ class ComedyCentralIE(InfoExtractor):  				'format': format,  				'thumbnail': None,  				'description': officialTitle, -				'player_url': playerUrl +				'player_url': None #playerUrl  			}  			results.append(info) @@ -2193,12 +2458,14 @@ class EscapistIE(InfoExtractor):  		self.report_extraction(showName)  		try: -			webPageBytes = urllib2.urlopen(url).read() +			webPage = urllib2.urlopen(url) +			webPageBytes = webPage.read() +			m = re.match(r'text/html; charset="?([^"]+)"?', webPage.headers['Content-Type']) +			webPage = webPageBytes.decode(m.group(1) if m else 'utf-8')  		except (urllib2.URLError, httplib.HTTPException, socket.error), err:  			self._downloader.trouble(u'ERROR: unable to download webpage: ' + unicode(err))  			return -		webPage = webPageBytes.decode('utf-8')  		descMatch = re.search('<meta name="description" content="([^"]*)"', webPage)  		description = unescapeHTML(descMatch.group(1))  		imgMatch = re.search('<meta property="og:image" content="([^"]*)"', webPage) @@ -2269,7 +2536,7 @@ class CollegeHumorIE(InfoExtractor):  		try:  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		m = re.search(r'id="video:(?P<internalvideoid>[0-9]+)"', webpage) @@ -2288,7 +2555,7 @@ class CollegeHumorIE(InfoExtractor):  		try:  			metaXml = urllib2.urlopen(xmlUrl).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video info XML: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video info XML: %s' % compat_str(err))  			return  		mdoc = xml.etree.ElementTree.fromstring(metaXml) @@ -2334,7 +2601,7 @@ class XVideosIE(InfoExtractor):  		try:  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		self.report_extraction(video_id) @@ -2357,11 +2624,11 @@ class XVideosIE(InfoExtractor):  		# Extract video thumbnail -		mobj = re.search(r'http://(?:img.*?\.)xvideos.com/videos/thumbs/[a-fA-F0-9]/[a-fA-F0-9]/[a-fA-F0-9]/([a-fA-F0-9.]+jpg)', webpage) +		mobj = re.search(r'http://(?:img.*?\.)xvideos.com/videos/thumbs/[a-fA-F0-9]+/[a-fA-F0-9]+/[a-fA-F0-9]+/[a-fA-F0-9]+/([a-fA-F0-9.]+jpg)', webpage)  		if mobj is None:  			self._downloader.trouble(u'ERROR: unable to extract video thumbnail')  			return -		video_thumbnail = mobj.group(1).decode('utf-8') +		video_thumbnail = mobj.group(0).decode('utf-8')  		info = {  			'id': video_id, @@ -2420,7 +2687,7 @@ class SoundcloudIE(InfoExtractor):  		try:  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		self.report_extraction('%s/%s' % (uploader, slug_title)) @@ -2447,7 +2714,7 @@ class SoundcloudIE(InfoExtractor):  		mobj = re.search('track-description-value"><p>(.*?)</p>', webpage)  		if mobj:  			description = mobj.group(1) -		 +  		# upload date  		upload_date = None  		mobj = re.search("pretty-date'>on ([\w]+ [\d]+, [\d]+ \d+:\d+)</abbr></h2>", webpage) @@ -2455,7 +2722,7 @@ class SoundcloudIE(InfoExtractor):  			try:  				upload_date = datetime.datetime.strptime(mobj.group(1), '%B %d, %Y %H:%M').strftime('%Y%m%d')  			except Exception, e: -				self._downloader.to_stderr(str(e)) +				self._downloader.to_stderr(compat_str(e))  		# for soundcloud, a request to a cross domain is required for cookies  		request = urllib2.Request('http://media.soundcloud.com/crossdomain.xml', std_headers) @@ -2499,7 +2766,7 @@ class InfoQIE(InfoExtractor):  		try:  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		self.report_extraction(url) @@ -2585,15 +2852,15 @@ class MixcloudIE(InfoExtractor):  		return None  	def _print_formats(self, formats): -		print 'Available formats:' +		print('Available formats:')  		for fmt in formats.keys():  			for b in formats[fmt]:  				try:  					ext = formats[fmt][b][0] -					print '%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1]) +					print('%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1]))  				except TypeError: # we have no bitrate info  					ext = formats[fmt][0] -					print '%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1]) +					print('%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1]))  					break  	def _real_extract(self, url): @@ -2613,7 +2880,7 @@ class MixcloudIE(InfoExtractor):  			self.report_download_json(file_url)  			jsonData = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: Unable to retrieve file: %s' % str(err)) +			self._downloader.trouble(u'ERROR: Unable to retrieve file: %s' % compat_str(err))  			return  		# parse JSON @@ -2797,7 +3064,7 @@ class MTVIE(InfoExtractor):  		try:  			webpage = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))  			return  		mobj = re.search(r'<meta name="mtv_vt" content="([^"]+)"/>', webpage) @@ -2830,7 +3097,7 @@ class MTVIE(InfoExtractor):  		try:  			metadataXml = urllib2.urlopen(request).read()  		except (urllib2.URLError, httplib.HTTPException, socket.error), err: -			self._downloader.trouble(u'ERROR: unable to download video metadata: %s' % str(err)) +			self._downloader.trouble(u'ERROR: unable to download video metadata: %s' % compat_str(err))  			return  		mdoc = xml.etree.ElementTree.fromstring(metadataXml) @@ -2857,3 +3124,314 @@ class MTVIE(InfoExtractor):  		}  		return [info] + + +class YoukuIE(InfoExtractor): + +	_VALID_URL =  r'(?:http://)?v\.youku\.com/v_show/id_(?P<ID>[A-Za-z0-9]+)\.html' +	IE_NAME = u'Youku' + +	def __init__(self, downloader=None): +		InfoExtractor.__init__(self, downloader) + +	def report_download_webpage(self, file_id): +		"""Report webpage download.""" +		self._downloader.to_screen(u'[Youku] %s: Downloading webpage' % file_id) + +	def report_extraction(self, file_id): +		"""Report information extraction.""" +		self._downloader.to_screen(u'[Youku] %s: Extracting information' % file_id) + +	def _gen_sid(self): +		nowTime = int(time.time() * 1000) +		random1 = random.randint(1000,1998) +		random2 = random.randint(1000,9999) + +		return "%d%d%d" %(nowTime,random1,random2) + +	def _get_file_ID_mix_string(self, seed): +		mixed = [] +		source = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890") +		seed = float(seed) +		for i in range(len(source)): +			seed  =  (seed * 211 + 30031 ) % 65536 +			index  =  math.floor(seed / 65536 * len(source) ) +			mixed.append(source[int(index)]) +			source.remove(source[int(index)]) +		#return ''.join(mixed) +		return mixed + +	def _get_file_id(self, fileId, seed): +		mixed = self._get_file_ID_mix_string(seed) +		ids = fileId.split('*') +		realId = [] +		for ch in ids: +			if ch: +				realId.append(mixed[int(ch)]) +		return ''.join(realId) + +	def _real_extract(self, url): +		mobj = re.match(self._VALID_URL, url) +		if mobj is None: +			self._downloader.trouble(u'ERROR: invalid URL: %s' % url) +			return +		video_id = mobj.group('ID') + +		info_url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + video_id + +		request = urllib2.Request(info_url, None, std_headers) +		try: +			self.report_download_webpage(video_id) +			jsondata = urllib2.urlopen(request).read() +		except (urllib2.URLError, httplib.HTTPException, socket.error) as err: +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err)) +			return + +		self.report_extraction(video_id) +		try: +			config = json.loads(jsondata) + +			video_title =  config['data'][0]['title'] +			seed = config['data'][0]['seed'] + +			format = self._downloader.params.get('format', None) +			supported_format = config['data'][0]['streamfileids'].keys() + +			if format is None or format == 'best': +				if 'hd2' in supported_format: +					format = 'hd2' +				else: +					format = 'flv' +				ext = u'flv' +			elif format == 'worst': +				format = 'mp4' +				ext = u'mp4' +			else: +				format = 'flv' +				ext = u'flv' + + +			fileid = config['data'][0]['streamfileids'][format] +			seg_number = len(config['data'][0]['segs'][format]) + +			keys=[] +			for i in xrange(seg_number): +				keys.append(config['data'][0]['segs'][format][i]['k']) + +			#TODO check error +			#youku only could be viewed from mainland china +		except: +			self._downloader.trouble(u'ERROR: unable to extract info section') +			return + +		files_info=[] +		sid = self._gen_sid() +		fileid = self._get_file_id(fileid, seed) + +		#column 8,9 of fileid represent the segment number +		#fileid[7:9] should be changed +		for index, key in enumerate(keys): + +			temp_fileid = '%s%02X%s' % (fileid[0:8], index, fileid[10:]) +			download_url = 'http://f.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key) + +			info = { +				'id': '%s_part%02d' % (video_id, index), +				'url': download_url, +				'uploader': None, +				'title': video_title, +				'ext': ext, +				'format': u'NA' +			} +			files_info.append(info) + +		return files_info + + +class XNXXIE(InfoExtractor): +	"""Information extractor for xnxx.com""" + +	_VALID_URL = r'^http://video\.xnxx\.com/video([0-9]+)/(.*)' +	IE_NAME = u'xnxx' +	VIDEO_URL_RE = r'flv_url=(.*?)&' +	VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM' +	VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&' + +	def report_webpage(self, video_id): +		"""Report information extraction""" +		self._downloader.to_screen(u'[%s] %s: Downloading webpage' % (self.IE_NAME, video_id)) + +	def report_extraction(self, video_id): +		"""Report information extraction""" +		self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id)) + +	def _real_extract(self, url): +		mobj = re.match(self._VALID_URL, url) +		if mobj is None: +			self._downloader.trouble(u'ERROR: invalid URL: %s' % url) +			return +		video_id = mobj.group(1).decode('utf-8') + +		self.report_webpage(video_id) + +		# Get webpage content +		try: +			webpage = urllib2.urlopen(url).read() +		except (urllib2.URLError, httplib.HTTPException, socket.error), err: +			self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % err) +			return + +		result = re.search(self.VIDEO_URL_RE, webpage) +		if result is None: +			self._downloader.trouble(u'ERROR: unable to extract video url') +			return +		video_url = urllib.unquote(result.group(1).decode('utf-8')) + +		result = re.search(self.VIDEO_TITLE_RE, webpage) +		if result is None: +			self._downloader.trouble(u'ERROR: unable to extract video title') +			return +		video_title = result.group(1).decode('utf-8') + +		result = re.search(self.VIDEO_THUMB_RE, webpage) +		if result is None: +			self._downloader.trouble(u'ERROR: unable to extract video thumbnail') +			return +		video_thumbnail = result.group(1).decode('utf-8') + +		info = {'id': video_id, +				'url': video_url, +				'uploader': None, +				'upload_date': None, +				'title': video_title, +				'ext': 'flv', +				'format': 'flv', +				'thumbnail': video_thumbnail, +				'description': None, +				'player_url': None} + +		return [info] + + +class GooglePlusIE(InfoExtractor): +	"""Information extractor for plus.google.com.""" + +	_VALID_URL = r'(?:https://)?plus\.google\.com/(?:\w+/)*?(\d+)/posts/(\w+)' +	IE_NAME = u'plus.google' + +	def __init__(self, downloader=None): +		InfoExtractor.__init__(self, downloader) + +	def report_extract_entry(self, url): +		"""Report downloading extry""" +		self._downloader.to_screen(u'[plus.google] Downloading entry: %s' % url.decode('utf-8')) + +	def report_date(self, upload_date): +		"""Report downloading extry""" +		self._downloader.to_screen(u'[plus.google] Entry date: %s' % upload_date) + +	def report_uploader(self, uploader): +		"""Report downloading extry""" +		self._downloader.to_screen(u'[plus.google] Uploader: %s' % uploader.decode('utf-8')) + +	def report_title(self, video_title): +		"""Report downloading extry""" +		self._downloader.to_screen(u'[plus.google] Title: %s' % video_title.decode('utf-8')) + +	def report_extract_vid_page(self, video_page): +		"""Report information extraction.""" +		self._downloader.to_screen(u'[plus.google] Extracting video page: %s' % video_page.decode('utf-8')) + +	def _real_extract(self, url): +		# Extract id from URL +		mobj = re.match(self._VALID_URL, url) +		if mobj is None: +			self._downloader.trouble(u'ERROR: Invalid URL: %s' % url) +			return + +		post_url = mobj.group(0) +		video_id = mobj.group(2) + +		video_extension = 'flv' + +		# Step 1, Retrieve post webpage to extract further information +		self.report_extract_entry(post_url) +		request = urllib2.Request(post_url) +		try: +			webpage = urllib2.urlopen(request).read() +		except (urllib2.URLError, httplib.HTTPException, socket.error), err: +			self._downloader.trouble(u'ERROR: Unable to retrieve entry webpage: %s' % compat_str(err)) +			return + +		# Extract update date +		upload_date = u'NA' +		pattern = 'title="Timestamp">(.*?)</a>' +		mobj = re.search(pattern, webpage) +		if mobj: +			upload_date = mobj.group(1) +			# Convert timestring to a format suitable for filename +			upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d") +			upload_date = upload_date.strftime('%Y%m%d') +		self.report_date(upload_date) + +		# Extract uploader +		uploader = u'NA' +		pattern = r'rel\="author".*?>(.*?)</a>' +		mobj = re.search(pattern, webpage) +		if mobj: +			uploader = mobj.group(1) +		self.report_uploader(uploader) + +		# Extract title +		# Get the first line for title +		video_title = u'NA' +		pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]' +		mobj = re.search(pattern, webpage) +		if mobj: +			video_title = mobj.group(1) +		self.report_title(video_title) + +		# Step 2, Stimulate clicking the image box to launch video +		pattern = '"(https\://plus\.google\.com/photos/.*?)",,"image/jpeg","video"\]' +		mobj = re.search(pattern, webpage) +		if mobj is None: +			self._downloader.trouble(u'ERROR: unable to extract video page URL') + +		video_page = mobj.group(1) +		request = urllib2.Request(video_page) +		try: +			webpage = urllib2.urlopen(request).read() +		except (urllib2.URLError, httplib.HTTPException, socket.error), err: +			self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % compat_str(err)) +			return +		self.report_extract_vid_page(video_page) + + +		# Extract video links on video page +		"""Extract video links of all sizes""" +		pattern = '\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"' +		mobj = re.findall(pattern, webpage) +		if len(mobj) == 0: +			self._downloader.trouble(u'ERROR: unable to extract video links') + +		# Sort in resolution +		links = sorted(mobj) + +		# Choose the lowest of the sort, i.e. highest resolution +		video_url = links[-1] +		# Only get the url. The resolution part in the tuple has no use anymore +		video_url = video_url[-1] +		# Treat escaped \u0026 style hex +		video_url = unicode(video_url, "unicode_escape") + + +		return [{ +			'id':		video_id.decode('utf-8'), +			'url':		video_url, +			'uploader':	uploader.decode('utf-8'), +			'upload_date':	upload_date.decode('utf-8'), +			'title':	video_title.decode('utf-8'), +			'ext':		video_extension.decode('utf-8'), +			'format':	u'NA', +			'player_url':	None, +		}] | 
