X-Git-Url: https://git.rapsys.eu/youtubedl/blobdiff_plain/6cd98fe80fb13ffc76b9e9bd8c01485644d1da8e..53b0892e0d78dd874acb6e22ed3e8c3f9fe175a4:/youtube-dl?ds=inline
diff --git a/youtube-dl b/youtube-dl
index e8b19c8..818a3a7 100755
--- a/youtube-dl
+++ b/youtube-dl
@@ -1,19 +1,32 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Author: Ricardo Garcia Gonzalez
-# Author: Danny Colligan
-# Author: Benjamin Johnson
-# Author: Vasyl' Vavrychuk
-# Author: Witold Baryluk
-# Author: PaweÅ Paprota
-# Author: Gergely Imreh
-# License: Public domain code
+
+__authors__  = (
+	'Ricardo Garcia Gonzalez',
+	'Danny Colligan',
+	'Benjamin Johnson',
+	'Vasyl\' Vavrychuk',
+	'Witold Baryluk',
+	'PaweÅ Paprota',
+	'Gergely Imreh',
+	'Rogério Brito',
+	'Philipp Hagemeister',
+	'Sören Schulze',
+	'Kevin Ngo',
+	'Ori Avtalion',
+	'shizeeg',
+	)
+
+__license__ = 'Public Domain'
+__version__ = '2012.01.05'
+
+UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
+
 import cookielib
-import ctypes
 import datetime
-import email.utils
 import gzip
 import htmlentitydefs
+import HTMLParser
 import httplib
 import locale
 import math
@@ -23,20 +36,42 @@ import os.path
 import re
 import socket
 import string
-import StringIO
 import subprocess
 import sys
 import time
 import urllib
 import urllib2
+import warnings
 import zlib
 
+if os.name == 'nt':
+	import ctypes
+
+try:
+	import email.utils
+except ImportError: # Python 2.4
+	import email.Utils
+try:
+	import cStringIO as StringIO
+except ImportError:
+	import StringIO
+
 # parse_qs was moved from the cgi module to the urlparse module recently.
 try:
 	from urlparse import parse_qs
 except ImportError:
 	from cgi import parse_qs
 
+try:
+	import lxml.etree
+except ImportError:
+	pass # Handled below
+
+try:
+	import xml.etree.ElementTree
+except ImportError: # Python<2.5: Not officially supported, but let it slip
+	warnings.warn('xml.etree.ElementTree support is missing. Consider upgrading to Python >= 2.5 if you get related errors.')
+
 std_headers = {
 	'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:5.0.1) Gecko/20100101 Firefox/5.0.1',
 	'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
@@ -45,7 +80,118 @@ std_headers = {
 	'Accept-Language': 'en-us,en;q=0.5',
 }
 
-simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii')
+try:
+	import json
+except ImportError: # Python <2.6, use trivialjson (https://github.com/phihag/trivialjson):
+	import re
+	class json(object):
+		@staticmethod
+		def loads(s):
+			s = s.decode('UTF-8')
+			def raiseError(msg, i):
+				raise ValueError(msg + ' at position ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]))
+			def skipSpace(i, expectMore=True):
+				while i < len(s) and s[i] in ' \t\r\n':
+					i += 1
+				if expectMore:
+					if i >= len(s):
+						raiseError('Premature end', i)
+				return i
+			def decodeEscape(match):
+				esc = match.group(1)
+				_STATIC = {
+					'"': '"',
+					'\\': '\\',
+					'/': '/',
+					'b': unichr(0x8),
+					'f': unichr(0xc),
+					'n': '\n',
+					'r': '\r',
+					't': '\t',
+				}
+				if esc in _STATIC:
+					return _STATIC[esc]
+				if esc[0] == 'u':
+					if len(esc) == 1+4:
+						return unichr(int(esc[1:5], 16))
+					if len(esc) == 5+6 and esc[5:7] == '\\u':
+						hi = int(esc[1:5], 16)
+						low = int(esc[7:11], 16)
+						return unichr((hi - 0xd800) * 0x400 + low - 0xdc00 + 0x10000)
+				raise ValueError('Unknown escape ' + str(esc))
+			def parseString(i):
+				i += 1
+				e = i
+				while True:
+					e = s.index('"', e)
+					bslashes = 0
+					while s[e-bslashes-1] == '\\':
+						bslashes += 1
+					if bslashes % 2 == 1:
+						e += 1
+						continue
+					break
+				rexp = re.compile(r'\\(u[dD][89aAbB][0-9a-fA-F]{2}\\u[0-9a-fA-F]{4}|u[0-9a-fA-F]{4}|.|$)')
+				stri = rexp.sub(decodeEscape, s[i:e])
+				return (e+1,stri)
+			def parseObj(i):
+				i += 1
+				res = {}
+				i = skipSpace(i)
+				if s[i] == '}': # Empty dictionary
+					return (i+1,res)
+				while True:
+					if s[i] != '"':
+						raiseError('Expected a string object key', i)
+					i,key = parseString(i)
+					i = skipSpace(i)
+					if i >= len(s) or s[i] != ':':
+						raiseError('Expected a colon', i)
+					i,val = parse(i+1)
+					res[key] = val
+					i = skipSpace(i)
+					if s[i] == '}':
+						return (i+1, res)
+					if s[i] != ',':
+						raiseError('Expected comma or closing curly brace', i)
+					i = skipSpace(i+1)
+			def parseArray(i):
+				res = []
+				i = skipSpace(i+1)
+				if s[i] == ']': # Empty array
+					return (i+1,res)
+				while True:
+					i,val = parse(i)
+					res.append(val)
+					i = skipSpace(i) # Raise exception if premature end
+					if s[i] == ']':
+						return (i+1, res)
+					if s[i] != ',':
+						raiseError('Expected a comma or closing bracket', i)
+					i = skipSpace(i+1)
+			def parseDiscrete(i):
+				for k,v in {'true': True, 'false': False, 'null': None}.items():
+					if s.startswith(k, i):
+						return (i+len(k), v)
+				raiseError('Not a boolean (or null)', i)
+			def parseNumber(i):
+				mobj = re.match('^(-?(0|[1-9][0-9]*)(\.[0-9]*)?([eE][+-]?[0-9]+)?)', s[i:])
+				if mobj is None:
+					raiseError('Not a number', i)
+				nums = mobj.group(1)
+				if '.' in nums or 'e' in nums or 'E' in nums:
+					return (i+len(nums), float(nums))
+				return (i+len(nums), int(nums))
+			CHARMAP = {'{': parseObj, '[': parseArray, '"': parseString, 't': parseDiscrete, 'f': parseDiscrete, 'n': parseDiscrete}
+			def parse(i):
+				i = skipSpace(i)
+				i,res = CHARMAP.get(s[i], parseNumber)(i)
+				i = skipSpace(i, False)
+				return (i,res)
+			i,res = parse(0)
+			if i < len(s):
+				raise ValueError('Extra data at end of input (index ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]) + ')')
+			return res
 
 def preferredencoding():
 	"""Get preferred encoding.
@@ -63,6 +209,7 @@ def preferredencoding():
 			yield pref
 	return yield_preferredencoding().next()
 
+
 def htmlentity_transform(matchobj):
 	"""Transforms an HTML entity to a Unicode character.
 
@@ -89,11 +236,13 @@ def htmlentity_transform(matchobj):
 	# Unknown entity in name, return its literal representation
 	return (u'&%s;' % entity)
 
+
 def sanitize_title(utitle):
 	"""Sanitizes a video title so it could be used as part of a filename."""
 	utitle = re.sub(ur'(?u)&(.+?);', htmlentity_transform, utitle)
 	return utitle.replace(unicode(os.sep), u'%')
 
+
 def sanitize_open(filename, open_mode):
 	"""Try to open the given filename, and slightly tweak it if this fails.
 
@@ -120,13 +269,35 @@ def sanitize_open(filename, open_mode):
 		stream = open(filename, open_mode)
 		return (stream, filename)
 
+
 def timeconvert(timestr):
-    """Convert RFC 2822 defined time string into system timestamp"""
-    timestamp = None
-    timetuple = email.utils.parsedate_tz(timestr)
-    if timetuple is not None:
-        timestamp = email.utils.mktime_tz(timetuple)
-    return timestamp
+	"""Convert RFC 2822 defined time string into system timestamp"""
+	timestamp = None
+	timetuple = email.utils.parsedate_tz(timestr)
+	if timetuple is not None:
+		timestamp = email.utils.mktime_tz(timetuple)
+	return timestamp
+
+def _simplify_title(title):
+	expr = re.compile(ur'[^\w\d_\-]+', flags=re.UNICODE)
+	return expr.sub(u'_', title).strip(u'_')
+
+def _orderedSet(iterable):
+	""" Remove all duplicates from the input iterable """
+	res = []
+	for el in iterable:
+		if el not in res:
+			res.append(el)
+	return res
+
+def _unescapeHTML(s):
+    """
+    @param s a string (of type unicode)
+    """
+    assert type(s) == type(u'')
+
+    htmlParser = HTMLParser.HTMLParser()
+    return htmlParser.unescape(s)
 
 class DownloadError(Exception):
 	"""Download Error exception.
@@ -137,6 +308,7 @@ class DownloadError(Exception):
 	"""
 	pass
 
+
 class SameFileError(Exception):
 	"""Same File exception.
 
@@ -145,6 +317,7 @@ class SameFileError(Exception):
 	"""
 	pass
 
+
 class PostProcessingError(Exception):
 	"""Post Processing exception.
 
@@ -153,6 +326,11 @@ class PostProcessingError(Exception):
 	"""
 	pass
 
+class MaxDownloadsReached(Exception):
+	""" --max-downloads limit has been reached. """
+	pass
+
+
 class UnavailableVideoError(Exception):
 	"""Unavailable Format exception.
 
@@ -161,6 +339,7 @@ class UnavailableVideoError(Exception):
 	"""
 	pass
 
+
 class ContentTooShortError(Exception):
 	"""Content Too Short exception.
 
@@ -176,6 +355,7 @@ class ContentTooShortError(Exception):
 		self.downloaded = downloaded
 		self.expected = expected
 
+
 class YoutubeDLHandler(urllib2.HTTPHandler):
 	"""Handler for HTTP requests and responses.
 
@@ -185,11 +365,11 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
 	a particular request, the original request in the program code only has
 	to include the HTTP header "Youtubedl-No-Compression", which will be
 	removed before making the real request.
-	
+
 	Part of this code was copied from:
 
-	  http://techknack.net/python-urllib2-handlers/
-	  
+	http://techknack.net/python-urllib2-handlers/
+
 	Andrew Rowls, the author of that code, agreed to release it to the
 	public domain.
 	"""
@@ -200,7 +380,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
 			return zlib.decompress(data, -zlib.MAX_WBITS)
 		except zlib.error:
 			return zlib.decompress(data)
-	
+
 	@staticmethod
 	def addinfourl_wrapper(stream, headers, url, code):
 		if hasattr(urllib2.addinfourl, 'getcode'):
@@ -208,7 +388,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
 		ret = urllib2.addinfourl(stream, headers, url)
 		ret.code = code
 		return ret
-	
+
 	def http_request(self, req):
 		for h in std_headers:
 			if h in req.headers:
@@ -234,6 +414,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
 			resp.msg = old_resp.msg
 		return resp
 
+
 class FileDownloader(object):
 	"""File Downloader class.
 
@@ -282,10 +463,14 @@ class FileDownloader(object):
 	noprogress:       Do not print the progress bar.
 	playliststart:    Playlist item to start at.
 	playlistend:      Playlist item to end at.
+	matchtitle:       Download only matching titles.
+	rejecttitle:      Reject downloads for matching titles.
 	logtostderr:      Log messages to stderr instead of stdout.
 	consoletitle:     Display progress in console window's titlebar.
 	nopart:           Do not use temporary .part files.
 	updatetime:       Use the Last-modified header to set output file timestamps.
+	writedescription: Write the video description to a .description file
+	writeinfojson:    Write the video description to a .info.json file
 	"""
 
 	params = None
@@ -304,16 +489,6 @@ class FileDownloader(object):
 		self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
 		self.params = params
 
-	@staticmethod
-	def pmkdir(filename):
-		"""Create directory components in filename. Similar to Unix "mkdir -p"."""
-		components = filename.split(os.sep)
-		aggregate = [os.sep.join(components[0:x]) for x in xrange(1, len(components))]
-		aggregate = ['%s%s' % (x, os.sep) for x in aggregate] # Finish names with separator
-		for dir in aggregate:
-			if not os.path.exists(dir):
-				os.mkdir(dir)
-
 	@staticmethod
 	def format_bytes(bytes):
 		if bytes is None:
@@ -325,7 +500,7 @@ class FileDownloader(object):
 		else:
 			exponent = long(math.log(bytes, 1024.0))
 		suffix = 'bkMGTPEZY'[exponent]
-		converted = float(bytes) / float(1024**exponent)
+		converted = float(bytes) / float(1024 ** exponent)
 		return '%.2f%s' % (converted, suffix)
 
 	@staticmethod
@@ -463,7 +638,7 @@ class FileDownloader(object):
 			os.rename(old_filename, new_filename)
 		except (IOError, OSError), err:
 			self.trouble(u'ERROR: unable to rename file')
-	
+
 	def try_utime(self, filename, last_modified_hdr):
 		"""Try to set the last-modified time of the given file."""
 		if last_modified_hdr is None:
@@ -475,11 +650,20 @@ class FileDownloader(object):
 			return
 		filetime = timeconvert(timestr)
 		if filetime is None:
-			return
+			return filetime
 		try:
-			os.utime(filename,(time.time(), filetime))
+			os.utime(filename, (time.time(), filetime))
 		except:
 			pass
+		return filetime
+
+	def report_writedescription(self, descfn):
+		""" Report that the description file is being written """
+		self.to_screen(u'[info] Writing video description to: %s' % descfn, ignore_encoding_errors=True)
+
+	def report_writeinfojson(self, infofn):
+		""" Report that the metadata file has been written """
+		self.to_screen(u'[info] Video description metadata as JSON to: %s' % infofn, ignore_encoding_errors=True)
 
 	def report_destination(self, filename):
 		"""Report destination filename."""
@@ -536,55 +720,116 @@ class FileDownloader(object):
 			self.trouble(u'ERROR: invalid system charset or erroneous output template')
 			return None
 
+	def _match_entry(self, info_dict):
+		""" Returns None iff the file should be downloaded """
+
+		title = info_dict['title']
+		matchtitle = self.params.get('matchtitle', False)
+		if matchtitle and not re.search(matchtitle, title, re.IGNORECASE):
+			return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
+		rejecttitle = self.params.get('rejecttitle', False)
+		if rejecttitle and re.search(rejecttitle, title, re.IGNORECASE):
+			return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
+		return None
+
 	def process_info(self, info_dict):
 		"""Process a single dictionary returned by an InfoExtractor."""
+
+		reason = self._match_entry(info_dict)
+		if reason is not None:
+			self.to_screen(u'[download] ' + reason)
+			return
+
+		max_downloads = self.params.get('max_downloads')
+		if max_downloads is not None:
+			if self._num_downloads > int(max_downloads):
+				raise MaxDownloadsReached()
+
 		filename = self.prepare_filename(info_dict)
+		
+		# Forced printings
+		if self.params.get('forcetitle', False):
+			print info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
+		if self.params.get('forceurl', False):
+			print info_dict['url'].encode(preferredencoding(), 'xmlcharrefreplace')
+		if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
+			print info_dict['thumbnail'].encode(preferredencoding(), 'xmlcharrefreplace')
+		if self.params.get('forcedescription', False) and 'description' in info_dict:
+			print info_dict['description'].encode(preferredencoding(), 'xmlcharrefreplace')
+		if self.params.get('forcefilename', False) and filename is not None:
+			print filename.encode(preferredencoding(), 'xmlcharrefreplace')
+		if self.params.get('forceformat', False):
+			print info_dict['format'].encode(preferredencoding(), 'xmlcharrefreplace')
+
 		# Do nothing else if in simulate mode
 		if self.params.get('simulate', False):
-			# Forced printings
-			if self.params.get('forcetitle', False):
-				print info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
-			if self.params.get('forceurl', False):
-				print info_dict['url'].encode(preferredencoding(), 'xmlcharrefreplace')
-			if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
-				print info_dict['thumbnail'].encode(preferredencoding(), 'xmlcharrefreplace')
-			if self.params.get('forcedescription', False) and 'description' in info_dict:
-				print info_dict['description'].encode(preferredencoding(), 'xmlcharrefreplace')
-			if self.params.get('forcefilename', False) and filename is not None:
-				print filename.encode(preferredencoding(), 'xmlcharrefreplace')
-
 			return
 
 		if filename is None:
 			return
-		if self.params.get('nooverwrites', False) and os.path.exists(filename):
-			self.to_stderr(u'WARNING: file exists and will be skipped')
-			return
 
 		try:
-			self.pmkdir(filename)
+			dn = os.path.dirname(filename)
+			if dn != '' and not os.path.exists(dn):
+				os.makedirs(dn)
 		except (OSError, IOError), err:
-			self.trouble(u'ERROR: unable to create directories: %s' % str(err))
+			self.trouble(u'ERROR: unable to create directory ' + unicode(err))
 			return
 
-		try:
-			success = self._do_download(filename, info_dict['url'].encode('utf-8'), info_dict.get('player_url', None))
-		except (OSError, IOError), err:
-			raise UnavailableVideoError
-		except (urllib2.URLError, httplib.HTTPException, socket.error), err:
-			self.trouble(u'ERROR: unable to download video data: %s' % str(err))
-			return
-		except (ContentTooShortError, ), err:
-			self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
-			return
+		if self.params.get('writedescription', False):
+			try:
+				descfn = filename + '.description'
+				self.report_writedescription(descfn)
+				descfile = open(descfn, 'wb')
+				try:
+					descfile.write(info_dict['description'].encode('utf-8'))
+				finally:
+					descfile.close()
+			except (OSError, IOError):
+				self.trouble(u'ERROR: Cannot write description file ' + descfn)
+				return
 
-		if success:
+		if self.params.get('writeinfojson', False):
+			infofn = filename + '.info.json'
+			self.report_writeinfojson(infofn)
+			try:
+				json.dump
+			except (NameError,AttributeError):
+				self.trouble(u'ERROR: No JSON encoder found. Update to Python 2.6+, setup a json module, or leave out --write-info-json.')
+				return
 			try:
-				self.post_process(filename, info_dict)
-			except (PostProcessingError), err:
-				self.trouble(u'ERROR: postprocessing: %s' % str(err))
+				infof = open(infofn, 'wb')
+				try:
+					json_info_dict = dict((k,v) for k,v in info_dict.iteritems() if not k in ('urlhandle',))
+					json.dump(json_info_dict, infof)
+				finally:
+					infof.close()
+			except (OSError, IOError):
+				self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
 				return
 
+		if not self.params.get('skip_download', False):
+			if self.params.get('nooverwrites', False) and os.path.exists(filename):
+				success = True
+			else:
+				try:
+					success = self._do_download(filename, info_dict)
+				except (OSError, IOError), err:
+					raise UnavailableVideoError
+				except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+					self.trouble(u'ERROR: unable to download video data: %s' % str(err))
+					return
+				except (ContentTooShortError, ), err:
+					self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
+					return
+	
+			if success:
+				try:
+					self.post_process(filename, info_dict)
+				except (PostProcessingError), err:
+					self.trouble(u'ERROR: postprocessing: %s' % str(err))
+					return
+
 	def download(self, url_list):
 		"""Download a given list of URLs."""
 		if len(url_list) > 1 and self.fixed_template():
@@ -644,6 +889,11 @@ class FileDownloader(object):
 			cursize = os.path.getsize(tmpfilename)
 			if prevsize == cursize and retval == 1:
 				break
+			 # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
+			if prevsize == cursize and retval == 2 and cursize > 1024:
+				self.to_screen(u'\r[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
+				retval = 0
+				break
 		if retval == 0:
 			self.to_screen(u'\r[rtmpdump] %s bytes' % os.path.getsize(tmpfilename))
 			self.try_rename(tmpfilename, filename)
@@ -652,7 +902,10 @@ class FileDownloader(object):
 			self.trouble(u'\nERROR: rtmpdump exited with code %d' % retval)
 			return False
 
-	def _do_download(self, filename, url, player_url):
+	def _do_download(self, filename, info_dict):
+		url = info_dict['url']
+		player_url = info_dict.get('player_url', None)
+
 		# Check file already present
 		if self.params.get('continuedl', False) and os.path.isfile(filename) and not self.params.get('nopart', False):
 			self.report_file_already_downloaded(filename)
@@ -664,7 +917,6 @@ class FileDownloader(object):
 
 		tmpfilename = self.temp_name(filename)
 		stream = None
-		open_mode = 'wb'
 
 		# Do not include the Accept-Encoding header
 		headers = {'Youtubedl-no-compression': 'True'}
@@ -677,17 +929,22 @@ class FileDownloader(object):
 		else:
 			resume_len = 0
 
-		# Request parameters in case of being able to resume
-		if self.params.get('continuedl', False) and resume_len != 0:
-			self.report_resuming_byte(resume_len)
-			request.add_header('Range','bytes=%d-' % resume_len)
-			open_mode = 'ab'
+		open_mode = 'wb'
+		if resume_len != 0:
+			if self.params.get('continuedl', False):
+				self.report_resuming_byte(resume_len)
+				request.add_header('Range','bytes=%d-' % resume_len)
+				open_mode = 'ab'
+			else:
+				resume_len = 0
 
 		count = 0
 		retries = self.params.get('retries', 0)
 		while count <= retries:
 			# Establish connection
 			try:
+				if count == 0 and 'urlhandle' in info_dict:
+					data = info_dict['urlhandle']
 				data = urllib2.urlopen(request)
 				break
 			except (urllib2.HTTPError, ), err:
@@ -706,7 +963,7 @@ class FileDownloader(object):
 					else:
 						# Examine the reported length
 						if (content_length is not None and
-						    (resume_len - 100 < long(content_length) < resume_len + 100)):
+								(resume_len - 100 < long(content_length) < resume_len + 100)):
 							# The file had already been fully downloaded.
 							# Explanation to the above condition: in issue #175 it was revealed that
 							# YouTube sometimes adds or removes a few bytes from the end of the file,
@@ -751,6 +1008,7 @@ class FileDownloader(object):
 			if stream is None:
 				try:
 					(stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
+					assert stream is not None
 					filename = self.undo_temp_name(tmpfilename)
 					self.report_destination(filename)
 				except (OSError, IOError), err:
@@ -764,14 +1022,20 @@ class FileDownloader(object):
 			block_size = self.best_block_size(after - before, len(data_block))
 
 			# Progress message
-			percent_str = self.calc_percent(byte_counter, data_len)
-			eta_str = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
 			speed_str = self.calc_speed(start, time.time(), byte_counter - resume_len)
-			self.report_progress(percent_str, data_len_str, speed_str, eta_str)
+			if data_len is None:
+				self.report_progress('Unknown %', data_len_str, speed_str, 'Unknown ETA')
+			else:
+				percent_str = self.calc_percent(byte_counter, data_len)
+				eta_str = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
+				self.report_progress(percent_str, data_len_str, speed_str, eta_str)
 
 			# Apply rate limit
 			self.slow_down(start, byte_counter - resume_len)
 
+		if stream is None:
+			self.trouble(u'\nERROR: Did not get any data blocks')
+			return False
 		stream.close()
 		self.report_finish()
 		if data_len is not None and byte_counter != data_len:
@@ -780,10 +1044,11 @@ class FileDownloader(object):
 
 		# Update file modification time
 		if self.params.get('updatetime', True):
-			self.try_utime(filename, data.info().get('last-modified', None))
+			info_dict['filetime'] = self.try_utime(filename, data.info().get('last-modified', None))
 
 		return True
 
+
 class InfoExtractor(object):
 	"""Information Extractor class.
 
@@ -814,9 +1079,8 @@ class InfoExtractor(object):
 	description:	One-line video description.
 
 	Subclasses of this one should re-define the _real_initialize() and
-	_real_extract() methods, as well as the suitable() static method.
-	Probably, they should also be instantiated and added to the main
-	downloader.
+	_real_extract() methods and define a _VALID_URL regexp.
+	Probably, they should also be added to the list of extractors.
 	"""
 
 	_ready = False
@@ -827,10 +1091,9 @@ class InfoExtractor(object):
 		self._ready = False
 		self.set_downloader(downloader)
 
-	@staticmethod
-	def suitable(url):
+	def suitable(self, url):
 		"""Receives a URL and returns True if suitable for this IE."""
-		return False
+		return re.match(self._VALID_URL, url) is not None
 
 	def initialize(self):
 		"""Initializes an instance (authentication, etc)."""
@@ -855,16 +1118,18 @@ class InfoExtractor(object):
 		"""Real extraction process. Redefine in subclasses."""
 		pass
 
+
 class YoutubeIE(InfoExtractor):
 	"""Information extractor for youtube.com."""
 
-	_VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$'
+	_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).+)?$'
 	_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'
 	_NETRC_MACHINE = 'youtube'
 	# Listed in order of quality
-	_available_formats = ['38', '37', '22', '45', '35', '34', '43', '18', '6', '5', '17', '13']
+	_available_formats = ['38', '37', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13']
+	_available_formats_prefer_free = ['38', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '17', '13']
 	_video_extensions = {
 		'13': '3gp',
 		'17': 'mp4',
@@ -873,12 +1138,25 @@ class YoutubeIE(InfoExtractor):
 		'37': 'mp4',
 		'38': 'video', # You actually don't know if this will be MOV, AVI or whatever
 		'43': 'webm',
+		'44': 'webm',
 		'45': 'webm',
 	}
-
-	@staticmethod
-	def suitable(url):
-		return (re.match(YoutubeIE._VALID_URL, url) is not None)
+	_video_dimensions = {
+		'5': '240x400',
+		'6': '???',
+		'13': '???',
+		'17': '144x176',
+		'18': '360x640',
+		'22': '720x1280',
+		'34': '360x640',
+		'35': '480x854',
+		'37': '1080x1920',
+		'38': '3072x4096',
+		'43': '360x640',
+		'44': '480x854',
+		'45': '720x1280',
+	}	
+	IE_NAME = u'youtube'
 
 	def report_lang(self):
 		"""Report attempt to set language."""
@@ -912,6 +1190,11 @@ class YoutubeIE(InfoExtractor):
 		"""Indicate the download will use the RTMP protocol."""
 		self._downloader.to_screen(u'[youtube] RTMP download detected')
 
+	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, 'flv'), self._video_dimensions.get(x, '???'))
+
 	def _real_initialize(self):
 		if self._downloader is None:
 			return
@@ -991,7 +1274,7 @@ class YoutubeIE(InfoExtractor):
 
 		# Get video webpage
 		self.report_video_webpage_download(video_id)
-		request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1' % video_id)
+		request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1' % video_id)
 		try:
 			video_webpage = urllib2.urlopen(request).read()
 		except (urllib2.URLError, httplib.HTTPException, socket.error), err:
@@ -1009,7 +1292,7 @@ class YoutubeIE(InfoExtractor):
 		self.report_video_info_webpage_download(video_id)
 		for el_type in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
 			video_info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en'
-					   % (video_id, el_type))
+					% (video_id, el_type))
 			request = urllib2.Request(video_info_url)
 			try:
 				video_info_webpage = urllib2.urlopen(request).read()
@@ -1044,8 +1327,7 @@ class YoutubeIE(InfoExtractor):
 		video_title = sanitize_title(video_title)
 
 		# simplified title
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
-		simple_title = simple_title.strip(ur'_')
+		simple_title = _simplify_title(video_title)
 
 		# thumbnail image
 		if 'thumbnail_url' not in video_info:
@@ -1067,11 +1349,19 @@ class YoutubeIE(InfoExtractor):
 					pass
 
 		# description
-		video_description = 'No description available.'
-		if self._downloader.params.get('forcedescription', False):
-			mobj = re.search(r'', video_webpage)
-			if mobj is not None:
-				video_description = mobj.group(1)
+		try:
+			lxml.etree
+		except NameError:
+			video_description = u'No description available.'
+			if self._downloader.params.get('forcedescription', False) or self._downloader.params.get('writedescription', False):
+				mobj = re.search(r'', video_webpage)
+				if mobj is not None:
+					video_description = mobj.group(1).decode('utf-8')
+		else:
+			html_parser = lxml.etree.HTMLParser(encoding='utf-8')
+			vwebpage_doc = lxml.etree.parse(StringIO.StringIO(video_webpage), html_parser)
+			video_description = u''.join(vwebpage_doc.xpath('id("eow-description")//text()'))
+			# TODO use another parser
 
 		# token
 		video_token = urllib.unquote_plus(video_info['token'][0])
@@ -1079,36 +1369,48 @@ class YoutubeIE(InfoExtractor):
 		# Decide which formats to download
 		req_format = self._downloader.params.get('format', None)
 
-		if 'url_encoded_fmt_stream_map' in video_info and len(video_info['url_encoded_fmt_stream_map']) >= 1:
+		if 'conn' in video_info and video_info['conn'][0].startswith('rtmp'):
+			self.report_rtmp_download()
+			video_url_list = [(None, video_info['conn'][0])]
+		elif 'url_encoded_fmt_stream_map' in video_info and len(video_info['url_encoded_fmt_stream_map']) >= 1:
 			url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',')
-			url_data = [dict(pairStr.split('=') for pairStr in uds.split('&')) for uds in url_data_strs]
-			url_map = dict((ud['itag'], urllib.unquote(ud['url'])) for ud in url_data)
+			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)
+
 			format_limit = self._downloader.params.get('format_limit', None)
-			if format_limit is not None and format_limit in self._available_formats:
-				format_list = self._available_formats[self._available_formats.index(format_limit):]
+			available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats
+			if format_limit is not None and format_limit in available_formats:
+				format_list = available_formats[available_formats.index(format_limit):]
 			else:
-				format_list = self._available_formats
+				format_list = available_formats
 			existing_formats = [x for x in format_list if x in url_map]
 			if len(existing_formats) == 0:
 				self._downloader.trouble(u'ERROR: no known formats available for video')
 				return
-			if req_format is None:
+			if self._downloader.params.get('listformats', None):
+				self._print_formats(existing_formats)
+				return
+			if req_format is None or req_format == 'best':
 				video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
-			elif req_format == '-1':
+			elif req_format == 'worst':
+				video_url_list = [(existing_formats[len(existing_formats)-1], url_map[existing_formats[len(existing_formats)-1]])] # worst quality
+			elif req_format in ('-1', 'all'):
 				video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
 			else:
-				# Specific format
-				if req_format not in url_map:
+				# Specific formats. We pick the first in a slash-delimeted sequence.
+				# For example, if '1/2/3/4' is requested and '2' and '4' are available, we pick '2'.
+				req_formats = req_format.split('/')
+				video_url_list = None
+				for rf in req_formats:
+					if rf in url_map:
+						video_url_list = [(rf, url_map[rf])]
+						break
+				if video_url_list is None:
 					self._downloader.trouble(u'ERROR: requested format not available')
 					return
-				video_url_list = [(req_format, url_map[req_format])] # Specific format
-
-		elif 'conn' in video_info and video_info['conn'][0].startswith('rtmp'):
-			self.report_rtmp_download()
-			video_url_list = [(None, video_info['conn'][0])]
-
 		else:
-			self._downloader.trouble(u'ERROR: no fmt_url_map or conn information found in video info')
+			self._downloader.trouble(u'ERROR: no conn or url_encoded_fmt_stream_map information found in video info')
 			return
 
 		for format_param, video_real_url in video_url_list:
@@ -1118,7 +1420,6 @@ class YoutubeIE(InfoExtractor):
 			# Extension
 			video_extension = self._video_extensions.get(format_param, 'flv')
 
-			# Find the video URL in fmt_url_map or conn paramters
 			try:
 				# Process video information
 				self._downloader.process_info({
@@ -1131,7 +1432,7 @@ class YoutubeIE(InfoExtractor):
 					'ext':		video_extension.decode('utf-8'),
 					'format':	(format_param is None and u'NA' or format_param.decode('utf-8')),
 					'thumbnail':	video_thumbnail.decode('utf-8'),
-					'description':	video_description.decode('utf-8'),
+					'description':	video_description,
 					'player_url':	player_url,
 				})
 			except UnavailableVideoError, err:
@@ -1145,15 +1446,12 @@ class MetacafeIE(InfoExtractor):
 	_DISCLAIMER = 'http://www.metacafe.com/family_filter/'
 	_FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user'
 	_youtube_ie = None
+	IE_NAME = u'metacafe'
 
 	def __init__(self, youtube_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._youtube_ie = youtube_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(MetacafeIE._VALID_URL, url) is not None)
-
 	def report_disclaimer(self):
 		"""Report disclaimer retrieval."""
 		self._downloader.to_screen(u'[metacafe] Retrieving disclaimer')
@@ -1287,14 +1585,11 @@ class DailymotionIE(InfoExtractor):
 	"""Information Extractor for Dailymotion"""
 
 	_VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)'
+	IE_NAME = u'dailymotion'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(DailymotionIE._VALID_URL, url) is not None)
-
 	def report_download_webpage(self, video_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'[dailymotion] %s: Downloading webpage' % video_id)
@@ -1303,9 +1598,6 @@ class DailymotionIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url):
 		# Extract id and simplified title from URL
 		mobj = re.match(self._VALID_URL, url)
@@ -1317,11 +1609,11 @@ class DailymotionIE(InfoExtractor):
 		self._downloader.increment_downloads()
 		video_id = mobj.group(1)
 
-		simple_title = mobj.group(2).decode('utf-8')
 		video_extension = 'flv'
 
 		# Retrieve video webpage to extract further information
 		request = urllib2.Request(url)
+		request.add_header('Cookie', 'family_filter=off')
 		try:
 			self.report_download_webpage(video_id)
 			webpage = urllib2.urlopen(request).read()
@@ -1331,25 +1623,30 @@ class DailymotionIE(InfoExtractor):
 
 		# Extract URL, uploader and title from webpage
 		self.report_extraction(video_id)
-		mobj = re.search(r'(?i)addVariable\(\"video\"\s*,\s*\"([^\"]*)\"\)', webpage)
+		mobj = re.search(r'(?i)addVariable\(\"sequence\"\s*,\s*\"([^\"]+?)\"\)', webpage)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: unable to extract media URL')
 			return
-		mediaURL = urllib.unquote(mobj.group(1))
+		sequence = urllib.unquote(mobj.group(1))
+		mobj = re.search(r',\"sdURL\"\:\"([^\"]+?)\",', sequence)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract media URL')
+			return
+		mediaURL = urllib.unquote(mobj.group(1)).replace('\\', '')
 
 		# if needed add http://www.dailymotion.com/ if relative URL
 
 		video_url = mediaURL
 
-		# ''
-		mobj = re.search(r'(?im)
Dailymotion\s*[\-:]\s*(.+?)', webpage)
+		mobj = re.search(r'', webpage)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: unable to extract title')
 			return
-		video_title = mobj.group(1).decode('utf-8')
+		video_title = _unescapeHTML(mobj.group('title').decode('utf-8'))
 		video_title = sanitize_title(video_title)
+		simple_title = _simplify_title(video_title)
 
-		mobj = re.search(r'(?im)(.+?)', webpage)
+		mobj = re.search(r'(?im)[^<]+?]+?>([^<]+?)', webpage)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
 			return
@@ -1371,18 +1668,16 @@ class DailymotionIE(InfoExtractor):
 		except UnavailableVideoError:
 			self._downloader.trouble(u'\nERROR: unable to download video')
 
+
 class GoogleIE(InfoExtractor):
 	"""Information extractor for video.google.com."""
 
 	_VALID_URL = r'(?:http://)?video\.google\.(?:com(?:\.au)?|co\.(?:uk|jp|kr|cr)|ca|de|es|fr|it|nl|pl)/videoplay\?docid=([^\&]+).*'
+	IE_NAME = u'video.google'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(GoogleIE._VALID_URL, url) is not None)
-
 	def report_download_webpage(self, video_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'[video.google] %s: Downloading webpage' % video_id)
@@ -1391,9 +1686,6 @@ class GoogleIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[video.google] %s: Extracting information' % video_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url):
 		# Extract id from URL
 		mobj = re.match(self._VALID_URL, url)
@@ -1437,7 +1729,7 @@ class GoogleIE(InfoExtractor):
 			return
 		video_title = mobj.group(1).decode('utf-8')
 		video_title = sanitize_title(video_title)
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+		simple_title = _simplify_title(video_title)
 
 		# Extract video description
 		mobj = re.search(r'([^<]*)', webpage)
@@ -1464,7 +1756,6 @@ class GoogleIE(InfoExtractor):
 		else:	# we need something to pass to process_info
 			video_thumbnail = ''
 
-
 		try:
 			# Process video information
 			self._downloader.process_info({
@@ -1486,14 +1777,11 @@ class PhotobucketIE(InfoExtractor):
 	"""Information extractor for photobucket.com."""
 
 	_VALID_URL = r'(?:http://)?(?:[a-z0-9]+\.)?photobucket\.com/.*[\?\&]current=(.*\.flv)'
+	IE_NAME = u'photobucket'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(PhotobucketIE._VALID_URL, url) is not None)
-
 	def report_download_webpage(self, video_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'[photobucket] %s: Downloading webpage' % video_id)
@@ -1502,9 +1790,6 @@ class PhotobucketIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[photobucket] %s: Extracting information' % video_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url):
 		# Extract id from URL
 		mobj = re.match(self._VALID_URL, url)
@@ -1543,7 +1828,7 @@ class PhotobucketIE(InfoExtractor):
 			return
 		video_title = mobj.group(1).decode('utf-8')
 		video_title = sanitize_title(video_title)
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+		simple_title = _simplify_title(vide_title)
 
 		video_uploader = mobj.group(2).decode('utf-8')
 
@@ -1571,14 +1856,11 @@ class YahooIE(InfoExtractor):
 	# _VPAGE_URL matches only the extractable '/watch/' URLs
 	_VALID_URL = r'(?:http://)?(?:[a-z]+\.)?video\.yahoo\.com/(?:watch|network)/([0-9]+)(?:/|\?v=)([0-9]+)(?:[#\?].*)?'
 	_VPAGE_URL = r'(?:http://)?video\.yahoo\.com/watch/([0-9]+)/([0-9]+)(?:[#\?].*)?'
+	IE_NAME = u'video.yahoo'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(YahooIE._VALID_URL, url) is not None)
-
 	def report_download_webpage(self, video_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'[video.yahoo] %s: Downloading webpage' % video_id)
@@ -1587,9 +1869,6 @@ class YahooIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[video.yahoo] %s: Extracting information' % video_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url, new_video=True):
 		# Extract ID from URL
 		mobj = re.match(self._VALID_URL, url)
@@ -1643,7 +1922,7 @@ class YahooIE(InfoExtractor):
 			self._downloader.trouble(u'ERROR: unable to extract video title')
 			return
 		video_title = mobj.group(1).decode('utf-8')
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+		simple_title = _simplify_title(video_title)
 
 		mobj = re.search(r'', webpage)
 		if mobj is None:
@@ -1664,7 +1943,8 @@ class YahooIE(InfoExtractor):
 			self._downloader.trouble(u'ERROR: unable to extract video description')
 			return
 		video_description = mobj.group(1).decode('utf-8')
-		if not video_description: video_description = 'No description available.'
+		if not video_description:
+			video_description = 'No description available.'
 
 		# Extract video height and width
 		mobj = re.search(r'', webpage)
@@ -1685,8 +1965,8 @@ class YahooIE(InfoExtractor):
 		yv_lg = 'R0xx6idZnW2zlrKP8xxAIR'  # not sure what this represents
 		yv_bitrate = '700'  # according to Wikipedia this is hard-coded
 		request = urllib2.Request('http://cosmos.bcst.yahoo.com/up/yep/process/getPlaylistFOP.php?node_id=' + video_id +
-				          '&tech=flash&mode=playlist&lg=' + yv_lg + '&bitrate=' + yv_bitrate + '&vidH=' + yv_video_height +
-					  '&vidW=' + yv_video_width + '&swf=as3&rd=video.yahoo.com&tk=null&adsupported=v1,v2,&eventid=1301797')
+				'&tech=flash&mode=playlist&lg=' + yv_lg + '&bitrate=' + yv_bitrate + '&vidH=' + yv_video_height +
+				'&vidW=' + yv_video_width + '&swf=as3&rd=video.yahoo.com&tk=null&adsupported=v1,v2,&eventid=1301797')
 		try:
 			self.report_download_webpage(video_id)
 			webpage = urllib2.urlopen(request).read()
@@ -1715,23 +1995,143 @@ class YahooIE(InfoExtractor):
 				'thumbnail':	video_thumbnail.decode('utf-8'),
 				'description':	video_description,
 				'thumbnail':	video_thumbnail,
-				'description':	video_description,
 				'player_url':	None,
 			})
 		except UnavailableVideoError:
 			self._downloader.trouble(u'\nERROR: unable to download video')
 
 
+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]+)'
+	IE_NAME = u'vimeo'
+
+	def __init__(self, downloader=None):
+		InfoExtractor.__init__(self, downloader)
+
+	def report_download_webpage(self, video_id):
+		"""Report webpage download."""
+		self._downloader.to_screen(u'[vimeo] %s: Downloading webpage' % video_id)
+
+	def report_extraction(self, video_id):
+		"""Report information extraction."""
+		self._downloader.to_screen(u'[vimeo] %s: Extracting information' % video_id)
+
+	def _real_extract(self, url, new_video=True):
+		# 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
+
+		# At this point we have a new video
+		self._downloader.increment_downloads()
+		video_id = mobj.group(1)
+
+		# Retrieve video webpage to extract further information
+		request = urllib2.Request("http://vimeo.com/moogaloop/load/clip:%s" % video_id, None, std_headers)
+		try:
+			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))
+			return
+
+		# Now we begin extracting as much information as we can from what we
+		# retrieved. First we extract the information common to all extractors,
+		# and latter we extract those that are Vimeo specific.
+		self.report_extraction(video_id)
+
+		# Extract title
+		mobj = re.search(r'(.*?)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video title')
+			return
+		video_title = mobj.group(1).decode('utf-8')
+		simple_title = _simplify_title(video_title)
+
+		# Extract uploader
+		mobj = re.search(r'http://vimeo.com/(.*?)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video uploader')
+			return
+		video_uploader = mobj.group(1).decode('utf-8')
+
+		# Extract video thumbnail
+		mobj = re.search(r'(.*?)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
+			return
+		video_thumbnail = mobj.group(1).decode('utf-8')
+
+		# # Extract video description
+		# mobj = re.search(r'', webpage)
+		# if mobj is None:
+		# 	self._downloader.trouble(u'ERROR: unable to extract video description')
+		# 	return
+		# video_description = mobj.group(1).decode('utf-8')
+		# if not video_description: video_description = 'No description available.'
+		video_description = 'Foo.'
+
+		# Vimeo specific: extract request signature
+		mobj = re.search(r'(.*?)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract request signature')
+			return
+		sig = mobj.group(1).decode('utf-8')
+
+		# Vimeo specific: extract video quality information
+		mobj = re.search(r'(\d+)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video quality information')
+			return
+		quality = mobj.group(1).decode('utf-8')
+
+		if int(quality) == 1:
+			quality = 'hd'
+		else:
+			quality = 'sd'
+
+		# Vimeo specific: Extract request signature expiration
+		mobj = re.search(r'(.*?)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract request signature expiration')
+			return
+		sig_exp = mobj.group(1).decode('utf-8')
+
+		video_url = "http://vimeo.com/moogaloop/play/clip:%s/%s/%s/?q=%s" % (video_id, sig, sig_exp, quality)
+
+		try:
+			# Process video information
+			self._downloader.process_info({
+				'id':		video_id.decode('utf-8'),
+				'url':		video_url,
+				'uploader':	video_uploader,
+				'upload_date':	u'NA',
+				'title':	video_title,
+				'stitle':	simple_title,
+				'ext':		u'mp4',
+				'thumbnail':	video_thumbnail.decode('utf-8'),
+				'description':	video_description,
+				'thumbnail':	video_thumbnail,
+				'description':	video_description,
+				'player_url':	None,
+			})
+		except UnavailableVideoError:
+			self._downloader.trouble(u'ERROR: unable to download video')
+
+
 class GenericIE(InfoExtractor):
 	"""Generic last-resort information extractor."""
 
+	_VALID_URL = r'.*'
+	IE_NAME = u'generic'
+
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return True
-
 	def report_download_webpage(self, video_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'WARNING: Falling back on generic information extractor.')
@@ -1741,9 +2141,6 @@ class GenericIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[generic] %s: Extracting information' % video_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url):
 		# At this point we have a new video
 		self._downloader.increment_downloads()
@@ -1779,11 +2176,11 @@ class GenericIE(InfoExtractor):
 			return
 
 		video_url = urllib.unquote(mobj.group(1))
-		video_id  = os.path.basename(video_url)
+		video_id = os.path.basename(video_url)
 
 		# here's a fun little line of code for you:
 		video_extension = os.path.splitext(video_id)[1][1:]
-		video_id        = os.path.splitext(video_id)[0]
+		video_id = os.path.splitext(video_id)[0]
 
 		# it's tempting to parse this further, but you would
 		# have to take into account all the variations like
@@ -1797,7 +2194,7 @@ class GenericIE(InfoExtractor):
 			return
 		video_title = mobj.group(1).decode('utf-8')
 		video_title = sanitize_title(video_title)
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+		simple_title = _simplify_title(video_title)
 
 		# video uploader is domain name
 		mobj = re.match(r'(?:https?://)?([^/]*)/.*', url)
@@ -1825,21 +2222,18 @@ class GenericIE(InfoExtractor):
 
 class YoutubeSearchIE(InfoExtractor):
 	"""Information Extractor for YouTube search queries."""
-	_VALID_QUERY = r'ytsearch(\d+|all)?:[\s\S]+'
+	_VALID_URL = r'ytsearch(\d+|all)?:[\s\S]+'
 	_TEMPLATE_URL = 'http://www.youtube.com/results?search_query=%s&page=%s&gl=US&hl=en'
 	_VIDEO_INDICATOR = r'href="/watch\?v=.+?"'
 	_MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*'
 	_youtube_ie = None
 	_max_youtube_results = 1000
+	IE_NAME = u'youtube:search'
 
 	def __init__(self, youtube_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._youtube_ie = youtube_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(YoutubeSearchIE._VALID_QUERY, url) is not None)
-
 	def report_download_page(self, query, pagenum):
 		"""Report attempt to download playlist page with given number."""
 		query = query.decode(preferredencoding())
@@ -1849,14 +2243,14 @@ class YoutubeSearchIE(InfoExtractor):
 		self._youtube_ie.initialize()
 
 	def _real_extract(self, query):
-		mobj = re.match(self._VALID_QUERY, query)
+		mobj = re.match(self._VALID_URL, query)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
 			return
 
 		prefix, query = query.split(':')
 		prefix = prefix[8:]
-		query  = query.encode('utf-8')
+		query = query.encode('utf-8')
 		if prefix == '':
 			self._download_n_results(query, 1)
 			return
@@ -1870,7 +2264,7 @@ class YoutubeSearchIE(InfoExtractor):
 					self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
 					return
 				elif n > self._max_youtube_results:
-					self._downloader.to_stderr(u'WARNING: ytsearch returns max %i results (you requested %i)'  % (self._max_youtube_results, n))
+					self._downloader.to_stderr(u'WARNING: ytsearch returns max %i results (you requested %i)' % (self._max_youtube_results, n))
 					n = self._max_youtube_results
 				self._download_n_results(query, n)
 				return
@@ -1914,23 +2308,21 @@ class YoutubeSearchIE(InfoExtractor):
 
 			pagenum = pagenum + 1
 
+
 class GoogleSearchIE(InfoExtractor):
 	"""Information Extractor for Google Video search queries."""
-	_VALID_QUERY = r'gvsearch(\d+|all)?:[\s\S]+'
+	_VALID_URL = r'gvsearch(\d+|all)?:[\s\S]+'
 	_TEMPLATE_URL = 'http://video.google.com/videosearch?q=%s+site:video.google.com&start=%s&hl=en'
 	_VIDEO_INDICATOR = r'videoplay\?docid=([^\&>]+)\&'
 	_MORE_PAGES_INDICATOR = r'Next'
 	_google_ie = None
 	_max_google_results = 1000
+	IE_NAME = u'video.google:search'
 
 	def __init__(self, google_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._google_ie = google_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(GoogleSearchIE._VALID_QUERY, url) is not None)
-
 	def report_download_page(self, query, pagenum):
 		"""Report attempt to download playlist page with given number."""
 		query = query.decode(preferredencoding())
@@ -1940,14 +2332,14 @@ class GoogleSearchIE(InfoExtractor):
 		self._google_ie.initialize()
 
 	def _real_extract(self, query):
-		mobj = re.match(self._VALID_QUERY, query)
+		mobj = re.match(self._VALID_URL, query)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
 			return
 
 		prefix, query = query.split(':')
 		prefix = prefix[8:]
-		query  = query.encode('utf-8')
+		query = query.encode('utf-8')
 		if prefix == '':
 			self._download_n_results(query, 1)
 			return
@@ -1961,7 +2353,7 @@ class GoogleSearchIE(InfoExtractor):
 					self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
 					return
 				elif n > self._max_google_results:
-					self._downloader.to_stderr(u'WARNING: gvsearch returns max %i results (you requested %i)'  % (self._max_google_results, n))
+					self._downloader.to_stderr(u'WARNING: gvsearch returns max %i results (you requested %i)' % (self._max_google_results, n))
 					n = self._max_google_results
 				self._download_n_results(query, n)
 				return
@@ -2005,23 +2397,21 @@ class GoogleSearchIE(InfoExtractor):
 
 			pagenum = pagenum + 1
 
+
 class YahooSearchIE(InfoExtractor):
 	"""Information Extractor for Yahoo! Video search queries."""
-	_VALID_QUERY = r'yvsearch(\d+|all)?:[\s\S]+'
+	_VALID_URL = r'yvsearch(\d+|all)?:[\s\S]+'
 	_TEMPLATE_URL = 'http://video.yahoo.com/search/?p=%s&o=%s'
 	_VIDEO_INDICATOR = r'href="http://video\.yahoo\.com/watch/([0-9]+/[0-9]+)"'
 	_MORE_PAGES_INDICATOR = r'\s*Next'
 	_yahoo_ie = None
 	_max_yahoo_results = 1000
+	IE_NAME = u'video.yahoo:search'
 
 	def __init__(self, yahoo_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._yahoo_ie = yahoo_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(YahooSearchIE._VALID_QUERY, url) is not None)
-
 	def report_download_page(self, query, pagenum):
 		"""Report attempt to download playlist page with given number."""
 		query = query.decode(preferredencoding())
@@ -2031,14 +2421,14 @@ class YahooSearchIE(InfoExtractor):
 		self._yahoo_ie.initialize()
 
 	def _real_extract(self, query):
-		mobj = re.match(self._VALID_QUERY, query)
+		mobj = re.match(self._VALID_URL, query)
 		if mobj is None:
 			self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
 			return
 
 		prefix, query = query.split(':')
 		prefix = prefix[8:]
-		query  = query.encode('utf-8')
+		query = query.encode('utf-8')
 		if prefix == '':
 			self._download_n_results(query, 1)
 			return
@@ -2052,7 +2442,7 @@ class YahooSearchIE(InfoExtractor):
 					self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
 					return
 				elif n > self._max_yahoo_results:
-					self._downloader.to_stderr(u'WARNING: yvsearch returns max %i results (you requested %i)'  % (self._max_yahoo_results, n))
+					self._downloader.to_stderr(u'WARNING: yvsearch returns max %i results (you requested %i)' % (self._max_yahoo_results, n))
 					n = self._max_yahoo_results
 				self._download_n_results(query, n)
 				return
@@ -2096,23 +2486,21 @@ class YahooSearchIE(InfoExtractor):
 
 			pagenum = pagenum + 1
 
+
 class YoutubePlaylistIE(InfoExtractor):
 	"""Information Extractor for YouTube playlists."""
 
-	_VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:(?:view_play_list|my_playlists|artist)\?.*?(p|a)=|user/.*?/user/|p/|user/.*?#[pg]/c/)([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)?([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 = r'/watch\?v=(.+?)&'
 	_MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*'
 	_youtube_ie = None
+	IE_NAME = u'youtube:playlist'
 
 	def __init__(self, youtube_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._youtube_ie = youtube_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(YoutubePlaylistIE._VALID_URL, url) is not None)
-
 	def report_download_page(self, playlist_id, pagenum):
 		"""Report attempt to download playlist page with given number."""
 		self._downloader.to_screen(u'[youtube] PL %s: Downloading page #%s' % (playlist_id, pagenum))
@@ -2146,7 +2534,8 @@ class YoutubePlaylistIE(InfoExtractor):
 
 		while True:
 			self.report_download_page(playlist_id, pagenum)
-			request = urllib2.Request(self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum))
+			url = self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum)
+			request = urllib2.Request(url)
 			try:
 				page = urllib2.urlopen(request).read()
 			except (urllib2.URLError, httplib.HTTPException, socket.error), err:
@@ -2172,28 +2561,26 @@ class YoutubePlaylistIE(InfoExtractor):
 			self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
 		return
 
+
 class YoutubeUserIE(InfoExtractor):
 	"""Information Extractor for YouTube users."""
 
-	_VALID_URL = r'(?:(?:(?:http://)?(?:\w+\.)?youtube.com/user/)|ytuser:)([A-Za-z0-9_-]+)'
+	_VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/user/)|ytuser:)([A-Za-z0-9_-]+)'
 	_TEMPLATE_URL = 'http://gdata.youtube.com/feeds/api/users/%s'
 	_GDATA_PAGE_SIZE = 50
 	_GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d'
-	_VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
+	_VIDEO_INDICATOR = r'/watch\?v=(.+?)[\<&]'
 	_youtube_ie = None
+	IE_NAME = u'youtube:user'
 
 	def __init__(self, youtube_ie, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 		self._youtube_ie = youtube_ie
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(YoutubeUserIE._VALID_URL, url) is not None)
-
 	def report_download_page(self, username, start_index):
 		"""Report attempt to download user page."""
 		self._downloader.to_screen(u'[youtube] user %s: Downloading video ids from %d to %d' %
-				           (username, start_index, start_index + self._GDATA_PAGE_SIZE))
+				(username, start_index, start_index + self._GDATA_PAGE_SIZE))
 
 	def _real_initialize(self):
 		self._youtube_ie.initialize()
@@ -2255,9 +2642,9 @@ class YoutubeUserIE(InfoExtractor):
 			video_ids = video_ids[playliststart:]
 		else:
 			video_ids = video_ids[playliststart:playlistend]
-			
+
 		self._downloader.to_screen("[youtube] user %s: Collected %d video ids (downloading %d of them)" %
-				           (username, all_ids_count, len(video_ids)))
+				(username, all_ids_count, len(video_ids)))
 
 		for video_id in video_ids:
 			self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % video_id)
@@ -2266,15 +2653,12 @@ class YoutubeUserIE(InfoExtractor):
 class DepositFilesIE(InfoExtractor):
 	"""Information extractor for depositfiles.com"""
 
-	_VALID_URL = r'(?:http://)?(?:\w+\.)?depositfiles.com/(?:../(?#locale))?files/(.+)'
+	_VALID_URL = r'(?:http://)?(?:\w+\.)?depositfiles\.com/(?:../(?#locale))?files/(.+)'
+	IE_NAME = u'DepositFiles'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(DepositFilesIE._VALID_URL, url) is not None)
-
 	def report_download_webpage(self, file_id):
 		"""Report webpage download."""
 		self._downloader.to_screen(u'[DepositFiles] %s: Downloading webpage' % file_id)
@@ -2283,9 +2667,6 @@ class DepositFilesIE(InfoExtractor):
 		"""Report information extraction."""
 		self._downloader.to_screen(u'[DepositFiles] %s: Extracting information' % file_id)
 
-	def _real_initialize(self):
-		return
-
 	def _real_extract(self, url):
 		# At this point we have a new file
 		self._downloader.increment_downloads()
@@ -2342,25 +2723,24 @@ class DepositFilesIE(InfoExtractor):
 		except UnavailableVideoError, err:
 			self._downloader.trouble(u'ERROR: unable to download file')
 
+
 class FacebookIE(InfoExtractor):
 	"""Information Extractor for Facebook"""
 
-	_VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook.com/video/video.php\?(?:.*?)v=(?P\d+)(?:.*)'
+	_VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook\.com/(?:video/video|photo)\.php\?(?:.*?)v=(?P\d+)(?:.*)'
 	_LOGIN_URL = 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php&'
 	_NETRC_MACHINE = 'facebook'
-	_available_formats = ['highqual', 'lowqual']
+	_available_formats = ['video', 'highqual', 'lowqual']
 	_video_extensions = {
+		'video': 'mp4',
 		'highqual': 'mp4',
 		'lowqual': 'mp4',
 	}
+	IE_NAME = u'facebook'
 
 	def __init__(self, downloader=None):
 		InfoExtractor.__init__(self, downloader)
 
-	@staticmethod
-	def suitable(url):
-		return (re.match(FacebookIE._VALID_URL, url) is not None)
-
 	def _reporter(self, message):
 		"""Add header and report message."""
 		self._downloader.to_screen(u'[facebook] %s' % message)
@@ -2380,10 +2760,9 @@ class FacebookIE(InfoExtractor):
 	def _parse_page(self, video_webpage):
 		"""Extract video information from page"""
 		# General data
-		data = {'title': r'class="video_title datawrap">(.*?)',
+		data = {'title': r'\("video_title", "(.*?)"\)',
 			'description': r'(.*?)
',
 			'owner': r'\("video_owner_name", "(.*?)"\)',
-			'upload_date': r'data-date="(.*?)"',
 			'thumbnail':  r'\("thumb_url", "(?P.*?)"\)',
 			}
 		video_info = {}
@@ -2485,9 +2864,7 @@ class FacebookIE(InfoExtractor):
 		video_title = video_title.decode('utf-8')
 		video_title = sanitize_title(video_title)
 
-		# simplified title
-		simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
-		simple_title = simple_title.strip(ur'_')
+		simple_title = _simplify_title(video_title)
 
 		# thumbnail image
 		if 'thumbnail' not in video_info:
@@ -2508,10 +2885,7 @@ class FacebookIE(InfoExtractor):
 					pass
 
 		# description
-		video_description = 'No description available.'
-		if (self._downloader.params.get('forcedescription', False) and
-		    'description' in video_info):
-			video_description = video_info['description']
+		video_description = video_info.get('description', 'No description available.')
 
 		url_map = video_info['video_urls']
 		if len(url_map.keys()) > 0:
@@ -2529,6 +2903,8 @@ class FacebookIE(InfoExtractor):
 				return
 			if req_format is None:
 				video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
+			elif req_format == 'worst':
+				video_url_list = [(existing_formats[len(existing_formats)-1], url_map[existing_formats[len(existing_formats)-1]])] # worst quality
 			elif req_format == '-1':
 				video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
 			else:
@@ -2546,7 +2922,6 @@ class FacebookIE(InfoExtractor):
 			# Extension
 			video_extension = self._video_extensions.get(format_param, 'mp4')
 
-			# Find the video URL in fmt_url_map or conn paramters
 			try:
 				# Process video information
 				self._downloader.process_info({
@@ -2565,14 +2940,959 @@ class FacebookIE(InfoExtractor):
 			except UnavailableVideoError, err:
 				self._downloader.trouble(u'\nERROR: unable to download video')
 
-class PostProcessor(object):
-	"""Post Processor class.
+class BlipTVIE(InfoExtractor):
+	"""Information extractor for blip.tv"""
 
-	PostProcessor objects can be added to downloaders with their
-	add_post_processor() method. When the downloader has finished a
-	successful download, it will take its internal chain of PostProcessors
-	and start calling the run() method on each one of them, first with
-	an initial argument and then with the returned value of the previous
+	_VALID_URL = r'^(?:https?://)?(?:\w+\.)?blip\.tv(/.+)$'
+	_URL_EXT = r'^.*\.([a-z0-9]+)$'
+	IE_NAME = u'blip.tv'
+
+	def report_extraction(self, file_id):
+		"""Report information extraction."""
+		self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, file_id))
+
+	def report_direct_download(self, title):
+		"""Report information extraction."""
+		self._downloader.to_screen(u'[%s] %s: Direct download detected' % (self.IE_NAME, title))
+
+	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
+
+		if '?' in url:
+			cchar = '&'
+		else:
+			cchar = '?'
+		json_url = url + cchar + 'skin=json&version=2&no_wrap=1'
+		request = urllib2.Request(json_url)
+		self.report_extraction(mobj.group(1))
+		info = None
+		try:
+			urlh = urllib2.urlopen(request)
+			if urlh.headers.get('Content-Type', '').startswith('video/'): # Direct download
+				basename = url.split('/')[-1]
+				title,ext = os.path.splitext(basename)
+				title = title.decode('UTF-8')
+				ext = ext.replace('.', '')
+				self.report_direct_download(title)
+				info = {
+					'id': title,
+					'url': url,
+					'title': title,
+					'stitle': _simplify_title(title),
+					'ext': ext,
+					'urlhandle': urlh
+				}
+		except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+			self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % 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))
+				return
+
+			try:
+				json_data = json.loads(json_code)
+				if 'Post' in json_data:
+					data = json_data['Post']
+				else:
+					data = json_data
+	
+				upload_date = datetime.datetime.strptime(data['datestamp'], '%m-%d-%y %H:%M%p').strftime('%Y%m%d')
+				video_url = data['media']['url']
+				umobj = re.match(self._URL_EXT, video_url)
+				if umobj is None:
+					raise ValueError('Can not determine filename extension')
+				ext = umobj.group(1)
+	
+				info = {
+					'id': data['item_id'],
+					'url': video_url,
+					'uploader': data['display_name'],
+					'upload_date': upload_date,
+					'title': data['title'],
+					'stitle': _simplify_title(data['title']),
+					'ext': ext,
+					'format': data['media']['mimeType'],
+					'thumbnail': data['thumbnailUrl'],
+					'description': data['description'],
+					'player_url': data['embedUrl']
+				}
+			except (ValueError,KeyError), err:
+				self._downloader.trouble(u'ERROR: unable to parse video information: %s' % repr(err))
+				return
+
+		self._downloader.increment_downloads()
+
+		try:
+			self._downloader.process_info(info)
+		except UnavailableVideoError, err:
+			self._downloader.trouble(u'\nERROR: unable to download video')
+
+
+class MyVideoIE(InfoExtractor):
+	"""Information Extractor for myvideo.de."""
+
+	_VALID_URL = r'(?:http://)?(?:www\.)?myvideo\.de/watch/([0-9]+)/([^?/]+).*'
+	IE_NAME = u'myvideo'
+
+	def __init__(self, downloader=None):
+		InfoExtractor.__init__(self, downloader)
+	
+	def report_download_webpage(self, video_id):
+		"""Report webpage download."""
+		self._downloader.to_screen(u'[myvideo] %s: Downloading webpage' % video_id)
+
+	def report_extraction(self, video_id):
+		"""Report information extraction."""
+		self._downloader.to_screen(u'[myvideo] %s: Extracting information' % video_id)
+
+	def _real_extract(self,url):
+		mobj = re.match(self._VALID_URL, url)
+		if mobj is None:
+			self._download.trouble(u'ERROR: invalid URL: %s' % url)
+			return
+
+		video_id = mobj.group(1)
+
+		# Get video webpage
+		request = urllib2.Request('http://www.myvideo.de/watch/%s' % video_id)
+		try:
+			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))
+			return
+
+		self.report_extraction(video_id)
+		mobj = re.search(r'',
+				 webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract media URL')
+			return
+		video_url = mobj.group(1) + ('/%s.flv' % video_id)
+
+		mobj = re.search('([^<]+)', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract title')
+			return
+
+		video_title = mobj.group(1)
+		video_title = sanitize_title(video_title)
+
+		simple_title = _simplify_title(video_title)
+
+		try:
+			self._downloader.process_info({
+				'id':		video_id,
+				'url':		video_url,
+				'uploader':	u'NA',
+				'upload_date':  u'NA',
+				'title':	video_title,
+				'stitle':	simple_title,
+				'ext':		u'flv',
+				'format':	u'NA',
+				'player_url':	None,
+			})
+		except UnavailableVideoError:
+			self._downloader.trouble(u'\nERROR: Unable to download video')
+
+class ComedyCentralIE(InfoExtractor):
+	"""Information extractor for The Daily Show and Colbert Report """
+
+	_VALID_URL = r'^(:(?Ptds|thedailyshow|cr|colbert|colbertnation|colbertreport))|(https?://)?(www\.)?(?Pthedailyshow|colbertnation)\.com/full-episodes/(?P.*)$'
+	IE_NAME = u'comedycentral'
+
+	def report_extraction(self, episode_id):
+		self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id)
+	
+	def report_config_download(self, episode_id):
+		self._downloader.to_screen(u'[comedycentral] %s: Downloading configuration' % episode_id)
+
+	def report_index_download(self, episode_id):
+		self._downloader.to_screen(u'[comedycentral] %s: Downloading show index' % episode_id)
+
+	def report_player_url(self, episode_id):
+		self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_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
+
+		if mobj.group('shortname'):
+			if mobj.group('shortname') in ('tds', 'thedailyshow'):
+				url = u'http://www.thedailyshow.com/full-episodes/'
+			else:
+				url = u'http://www.colbertnation.com/full-episodes/'
+			mobj = re.match(self._VALID_URL, url)
+			assert mobj is not None
+
+		dlNewest = not mobj.group('episode')
+		if dlNewest:
+			epTitle = mobj.group('showname')
+		else:
+			epTitle = mobj.group('episode')
+
+		req = urllib2.Request(url)
+		self.report_extraction(epTitle)
+		try:
+			htmlHandle = urllib2.urlopen(req)
+			html = htmlHandle.read()
+		except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+			self._downloader.trouble(u'ERROR: unable to download webpage: %s' % unicode(err))
+			return
+		if dlNewest:
+			url = htmlHandle.geturl()
+			mobj = re.match(self._VALID_URL, url)
+			if mobj is None:
+				self._downloader.trouble(u'ERROR: Invalid redirected URL: ' + url)
+				return
+			if mobj.group('episode') == '':
+				self._downloader.trouble(u'ERROR: Redirected URL is still not specific: ' + url)
+				return
+			epTitle = mobj.group('episode')
+
+		mMovieParams = re.findall('(?:[^/]+)/(?P[^/?]+)[/?]?.*$'
+	IE_NAME = u'escapist'
+
+	def report_extraction(self, showName):
+		self._downloader.to_screen(u'[escapist] %s: Extracting information' % showName)
+
+	def report_config_download(self, showName):
+		self._downloader.to_screen(u'[escapist] %s: Downloading configuration' % showName)
+
+	def _real_extract(self, url):
+		htmlParser = HTMLParser.HTMLParser()
+
+		mobj = re.match(self._VALID_URL, url)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+			return
+		showName = mobj.group('showname')
+		videoId = mobj.group('episode')
+
+		self.report_extraction(showName)
+		try:
+			webPage = urllib2.urlopen(url).read()
+		except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+			self._downloader.trouble(u'ERROR: unable to download webpage: ' + unicode(err))
+			return
+
+		descMatch = re.search('[0-9]+)/(?P.*)$'
+	IE_NAME = u'collegehumor'
+
+	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):
+		htmlParser = HTMLParser.HTMLParser()
+
+		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('videoid')
+
+		self.report_webpage(video_id)
+		request = urllib2.Request(url)
+		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))
+			return
+
+		m = re.search(r'id="video:(?P[0-9]+)"', webpage)
+		if m is None:
+			self._downloader.trouble(u'ERROR: Cannot extract internal video ID')
+			return
+		internal_video_id = m.group('internalvideoid')
+
+		info = {
+			'id': video_id,
+			'internal_id': internal_video_id,
+		}
+
+		self.report_extraction(video_id)
+		xmlUrl = 'http://www.collegehumor.com/moogaloop/video:' + internal_video_id
+		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))
+			return
+
+		mdoc = xml.etree.ElementTree.fromstring(metaXml)
+		try:
+			videoNode = mdoc.findall('./video')[0]
+			info['description'] = videoNode.findall('./description')[0].text
+			info['title'] = videoNode.findall('./caption')[0].text
+			info['stitle'] = _simplify_title(info['title'])
+			info['url'] = videoNode.findall('./file')[0].text
+			info['thumbnail'] = videoNode.findall('./thumbnail')[0].text
+			info['ext'] = info['url'].rpartition('.')[2]
+			info['format'] = info['ext']
+		except IndexError:
+			self._downloader.trouble(u'\nERROR: Invalid metadata XML file')
+			return
+
+		self._downloader.increment_downloads()
+
+		try:
+			self._downloader.process_info(info)
+		except UnavailableVideoError, err:
+			self._downloader.trouble(u'\nERROR: unable to download video')
+
+
+class XVideosIE(InfoExtractor):
+	"""Information extractor for xvideos.com"""
+
+	_VALID_URL = r'^(?:https?://)?(?:www\.)?xvideos\.com/video([0-9]+)(?:.*)'
+	IE_NAME = u'xvideos'
+
+	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):
+		htmlParser = HTMLParser.HTMLParser()
+
+		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)
+
+		request = urllib2.Request(r'http://www.xvideos.com/video' + video_id)
+		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))
+			return
+
+		self.report_extraction(video_id)
+
+
+		# Extract video URL
+		mobj = re.search(r'flv_url=(.+?)&', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video url')
+			return
+		video_url = urllib2.unquote(mobj.group(1).decode('utf-8'))
+
+
+		# Extract title
+		mobj = re.search(r'(.*?)\s+-\s+XVID', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video title')
+			return
+		video_title = mobj.group(1).decode('utf-8')
+
+
+		# 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)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
+			return
+		video_thumbnail = mobj.group(1).decode('utf-8')
+
+
+
+		self._downloader.increment_downloads()
+		info = {
+			'id': video_id,
+			'url': video_url,
+			'uploader': None,
+			'upload_date': None,
+			'title': video_title,
+			'stitle': _simplify_title(video_title),
+			'ext': 'flv',
+			'format': 'flv',
+			'thumbnail': video_thumbnail,
+			'description': None,
+			'player_url': None,
+		}
+
+		try:
+			self._downloader.process_info(info)
+		except UnavailableVideoError, err:
+			self._downloader.trouble(u'\nERROR: unable to download ' + video_id)
+
+
+class SoundcloudIE(InfoExtractor):
+	"""Information extractor for soundcloud.com
+	   To access the media, the uid of the song and a stream token
+	   must be extracted from the page source and the script must make
+	   a request to media.soundcloud.com/crossdomain.xml. Then
+	   the media can be grabbed by requesting from an url composed
+	   of the stream token and uid
+	 """
+
+	_VALID_URL = r'^(?:https?://)?(?:www\.)?soundcloud\.com/([\w\d-]+)/([\w\d-]+)'
+	IE_NAME = u'soundcloud'
+
+	def __init__(self, downloader=None):
+		InfoExtractor.__init__(self, downloader)
+
+	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):
+		htmlParser = HTMLParser.HTMLParser()
+
+		mobj = re.match(self._VALID_URL, url)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+			return
+
+		# extract uploader (which is in the url)
+		uploader = mobj.group(1).decode('utf-8')
+		# extract simple title (uploader + slug of song title)
+		slug_title =  mobj.group(2).decode('utf-8')
+		simple_title = uploader + '-' + slug_title
+
+		self.report_webpage('%s/%s' % (uploader, slug_title))
+
+		request = urllib2.Request('http://soundcloud.com/%s/%s' % (uploader, slug_title))
+		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))
+			return
+
+		self.report_extraction('%s/%s' % (uploader, slug_title))
+
+		# extract uid and stream token that soundcloud hands out for access
+		mobj = re.search('"uid":"([\w\d]+?)".*?stream_token=([\w\d]+)', webpage)
+		if mobj:
+			video_id = mobj.group(1)
+			stream_token = mobj.group(2)
+
+		# extract unsimplified title
+		mobj = re.search('"title":"(.*?)",', webpage)
+		if mobj:
+			title = mobj.group(1)
+
+		# construct media url (with uid/token)
+		mediaURL = "http://media.soundcloud.com/stream/%s?stream_token=%s"
+		mediaURL = mediaURL % (video_id, stream_token)
+
+		# description
+		description = u'No description available'
+		mobj = re.search('track-description-value">(.*?)
', webpage)
+		if mobj:
+			description = mobj.group(1)
+		
+		# upload date
+		upload_date = None
+		mobj = re.search("pretty-date'>on ([\w]+ [\d]+, [\d]+ \d+:\d+)", webpage)
+		if mobj:
+			try:
+				upload_date = datetime.datetime.strptime(mobj.group(1), '%B %d, %Y %H:%M').strftime('%Y%m%d')
+			except Exception, e:
+				print 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)
+
+		try:
+			self._downloader.process_info({
+				'id':		video_id.decode('utf-8'),
+				'url':		mediaURL,
+				'uploader':	uploader.decode('utf-8'),
+				'upload_date':  upload_date,
+				'title':	simple_title.decode('utf-8'),
+				'stitle':	simple_title.decode('utf-8'),
+				'ext':		u'mp3',
+				'format':	u'NA',
+				'player_url':	None,
+				'description': description.decode('utf-8')
+			})
+		except UnavailableVideoError:
+			self._downloader.trouble(u'\nERROR: unable to download video')
+
+
+class InfoQIE(InfoExtractor):
+	"""Information extractor for infoq.com"""
+
+	_VALID_URL = r'^(?:https?://)?(?:www\.)?infoq\.com/[^/]+/[^/]+$'
+	IE_NAME = u'infoq'
+
+	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):
+		htmlParser = HTMLParser.HTMLParser()
+
+		mobj = re.match(self._VALID_URL, url)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+			return
+
+		self.report_webpage(url)
+
+		request = urllib2.Request(url)
+		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))
+			return
+
+		self.report_extraction(url)
+
+
+		# Extract video URL
+		mobj = re.search(r"jsclassref='([^']*)'", webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video url')
+			return
+		video_url = 'rtmpe://video.infoq.com/cfx/st/' + urllib2.unquote(mobj.group(1).decode('base64'))
+
+
+		# Extract title
+		mobj = re.search(r'contentTitle = "(.*?)";', webpage)
+		if mobj is None:
+			self._downloader.trouble(u'ERROR: unable to extract video title')
+			return
+		video_title = mobj.group(1).decode('utf-8')
+
+		# Extract description
+		video_description = u'No description available.'
+		mobj = re.search(r'', webpage)
+		if mobj is not None:
+			video_description = mobj.group(1).decode('utf-8')
+
+		video_filename = video_url.split('/')[-1]
+		video_id, extension = video_filename.split('.')
+
+		self._downloader.increment_downloads()
+		info = {
+			'id': video_id,
+			'url': video_url,
+			'uploader': None,
+			'upload_date': None,
+			'title': video_title,
+			'stitle': _simplify_title(video_title),
+			'ext': extension,
+			'format': extension, # Extension is always(?) mp4, but seems to be flv
+			'thumbnail': None,
+			'description': video_description,
+			'player_url': None,
+		}
+
+		try:
+			self._downloader.process_info(info)
+		except UnavailableVideoError, err:
+			self._downloader.trouble(u'\nERROR: unable to download ' + video_url)
+
+class MixcloudIE(InfoExtractor):
+	"""Information extractor for www.mixcloud.com"""
+	_VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)'
+	IE_NAME = u'mixcloud'
+
+	def __init__(self, downloader=None):
+		InfoExtractor.__init__(self, downloader)
+
+	def report_download_json(self, file_id):
+		"""Report JSON download."""
+		self._downloader.to_screen(u'[%s] Downloading json' % self.IE_NAME)
+
+	def report_extraction(self, file_id):
+		"""Report information extraction."""
+		self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, file_id))
+
+	def get_urls(self, jsonData, fmt, bitrate='best'):
+		"""Get urls from 'audio_formats' section in json"""
+		file_url = None
+		try:
+			bitrate_list = jsonData[fmt]
+			if bitrate is None or bitrate == 'best' or bitrate not in bitrate_list:
+				bitrate = max(bitrate_list) # select highest
+
+			url_list = jsonData[fmt][bitrate]
+		except TypeError: # we have no bitrate info.
+			url_list = jsonData[fmt]
+				
+		return url_list
+
+	def check_urls(self, url_list):
+		"""Returns 1st active url from list"""
+		for url in url_list:
+			try:
+				urllib2.urlopen(url)
+				return url
+			except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+				url = None
+
+		return None
+
+	def _print_formats(self, 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])
+				except TypeError: # we have no bitrate info
+					ext = formats[fmt][0]
+					print '%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1])
+					break
+
+	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
+		# extract uploader & filename from url
+		uploader = mobj.group(1).decode('utf-8')
+		file_id = uploader + "-" + mobj.group(2).decode('utf-8')
+
+		# construct API request
+		file_url = 'http://www.mixcloud.com/api/1/cloudcast/' + '/'.join(url.split('/')[-3:-1]) + '.json'
+		# retrieve .json file with links to files
+		request = urllib2.Request(file_url)
+		try:
+			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))
+			return
+
+		# parse JSON
+		json_data = json.loads(jsonData)
+		player_url = json_data['player_swf_url']
+		formats = dict(json_data['audio_formats'])
+
+		req_format = self._downloader.params.get('format', None)
+		bitrate = None
+
+		if self._downloader.params.get('listformats', None):
+			self._print_formats(formats)
+			return
+
+		if req_format is None or req_format == 'best':
+			for format_param in formats.keys():
+				url_list = self.get_urls(formats, format_param)
+				# check urls
+				file_url = self.check_urls(url_list)
+				if file_url is not None:
+					break # got it!
+		else:
+			if req_format not in formats.keys():
+				self._downloader.trouble(u'ERROR: format is not available')
+				return
+
+			url_list = self.get_urls(formats, req_format)
+			file_url = self.check_urls(url_list)
+			format_param = req_format
+
+		# We have audio
+		self._downloader.increment_downloads()
+		try:
+			# Process file information
+			self._downloader.process_info({
+				'id':		file_id.decode('utf-8'),
+				'url':		file_url.decode('utf-8'),
+				'uploader':	uploader.decode('utf-8'),
+				'upload_date':	u'NA',
+				'title':	json_data['name'],
+				'stitle':	_simplify_title(json_data['name']),
+				'ext':		file_url.split('.')[-1].decode('utf-8'),
+				'format':	(format_param is None and u'NA' or format_param.decode('utf-8')),
+				'thumbnail':    json_data['thumbnail_url'],
+				'description':  json_data['description'],
+				'player_url':	player_url.decode('utf-8'),
+			})
+		except UnavailableVideoError, err:
+			self._downloader.trouble(u'ERROR: unable to download file')
+
+class StanfordOpenClassroomIE(InfoExtractor):
+	"""Information extractor for Stanford's Open ClassRoom"""
+
+	_VALID_URL = r'^(?:https?://)?openclassroom.stanford.edu(?P/?|(/MainFolder/(?:HomePage|CoursePage|VideoPage)\.php([?]course=(?P[^&]+)(&video=(?P