Clean up superfluous whitespace
[youtube-dl] / youtube-dl
index 32bd99e98e4f1e575fdb2a2881f833b6adae3ce3..bf77fb90d3d782a7169e7c60d9a57b5c7f2eb4af 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-__author__  = (
+__authors__  = (
        'Ricardo Garcia Gonzalez',
        'Danny Colligan',
        'Benjamin Johnson',
        'Ricardo Garcia Gonzalez',
        'Danny Colligan',
        'Benjamin Johnson',
@@ -9,22 +9,36 @@ __author__  = (
        'Witold Baryluk',
        'Paweł Paprota',
        'Gergely Imreh',
        'Witold Baryluk',
        'Paweł Paprota',
        'Gergely Imreh',
+       'Rogério Brito',
+       'Philipp Hagemeister',
+       'Sören Schulze',
+       'Kevin Ngo',
+       'Ori Avtalion',
+       'shizeeg',
+       'Filippo Valsorda',
        )
 
 __license__ = 'Public Domain'
        )
 
 __license__ = 'Public Domain'
-__version__ = '2011.08.24-phihag'
+__version__ = '2012.02.27'
+
+UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
+
 
 import cookielib
 import datetime
 
 import cookielib
 import datetime
+import getpass
 import gzip
 import htmlentitydefs
 import gzip
 import htmlentitydefs
+import HTMLParser
 import httplib
 import locale
 import math
 import netrc
 import httplib
 import locale
 import math
 import netrc
+import optparse
 import os
 import os.path
 import re
 import os
 import os.path
 import re
+import shlex
 import socket
 import string
 import subprocess
 import socket
 import string
 import subprocess
@@ -58,6 +72,11 @@ try:
 except ImportError:
        pass # Handled below
 
 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',
 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',
@@ -66,8 +85,6 @@ std_headers = {
        'Accept-Language': 'en-us,en;q=0.5',
 }
 
        '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):
 try:
        import json
 except ImportError: # Python <2.6, use trivialjson (https://github.com/phihag/trivialjson):
@@ -197,6 +214,7 @@ def preferredencoding():
                        yield pref
        return yield_preferredencoding().next()
 
                        yield pref
        return yield_preferredencoding().next()
 
+
 def htmlentity_transform(matchobj):
        """Transforms an HTML entity to a Unicode character.
 
 def htmlentity_transform(matchobj):
        """Transforms an HTML entity to a Unicode character.
 
@@ -223,11 +241,13 @@ def htmlentity_transform(matchobj):
        # Unknown entity in name, return its literal representation
        return (u'&%s;' % entity)
 
        # 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_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.
 
 def sanitize_open(filename, open_mode):
        """Try to open the given filename, and slightly tweak it if this fails.
 
@@ -244,23 +264,60 @@ def sanitize_open(filename, open_mode):
                                import msvcrt
                                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
                        return (sys.stdout, filename)
                                import msvcrt
                                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
                        return (sys.stdout, filename)
-               stream = open(filename, open_mode)
+               stream = open(_encodeFilename(filename), open_mode)
                return (stream, filename)
        except (IOError, OSError), err:
                # In case of error, try to remove win32 forbidden chars
                filename = re.sub(ur'[/<>:"\|\?\*]', u'#', filename)
 
                # An exception here should be caught in the caller
                return (stream, filename)
        except (IOError, OSError), err:
                # In case of error, try to remove win32 forbidden chars
                filename = re.sub(ur'[/<>:"\|\?\*]', u'#', filename)
 
                # An exception here should be caught in the caller
-               stream = open(filename, open_mode)
+               stream = open(_encodeFilename(filename), open_mode)
                return (stream, filename)
 
                return (stream, filename)
 
+
 def timeconvert(timestr):
 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)
+
+def _encodeFilename(s):
+       """
+       @param s The name of the file (of type unicode)
+       """
+
+       assert type(s) == type(u'')
+
+       if sys.platform == 'win32' and sys.getwindowsversion().major >= 5:
+               # Pass u'' directly to use Unicode APIs on Windows 2000 and up
+               # (Detecting Windows NT 4 is tricky because 'major >= 4' would
+               # match Windows 9x series as well. Besides, NT 4 is obsolete.)
+               return s
+       else:
+               return s.encode(sys.getfilesystemencoding(), 'ignore')
 
 class DownloadError(Exception):
        """Download Error exception.
 
 class DownloadError(Exception):
        """Download Error exception.
@@ -271,6 +328,7 @@ class DownloadError(Exception):
        """
        pass
 
        """
        pass
 
+
 class SameFileError(Exception):
        """Same File exception.
 
 class SameFileError(Exception):
        """Same File exception.
 
@@ -279,6 +337,7 @@ class SameFileError(Exception):
        """
        pass
 
        """
        pass
 
+
 class PostProcessingError(Exception):
        """Post Processing exception.
 
 class PostProcessingError(Exception):
        """Post Processing exception.
 
@@ -287,6 +346,11 @@ class PostProcessingError(Exception):
        """
        pass
 
        """
        pass
 
+class MaxDownloadsReached(Exception):
+       """ --max-downloads limit has been reached. """
+       pass
+
+
 class UnavailableVideoError(Exception):
        """Unavailable Format exception.
 
 class UnavailableVideoError(Exception):
        """Unavailable Format exception.
 
@@ -295,6 +359,7 @@ class UnavailableVideoError(Exception):
        """
        pass
 
        """
        pass
 
+
 class ContentTooShortError(Exception):
        """Content Too Short exception.
 
 class ContentTooShortError(Exception):
        """Content Too Short exception.
 
@@ -310,6 +375,7 @@ class ContentTooShortError(Exception):
                self.downloaded = downloaded
                self.expected = expected
 
                self.downloaded = downloaded
                self.expected = expected
 
+
 class YoutubeDLHandler(urllib2.HTTPHandler):
        """Handler for HTTP requests and responses.
 
 class YoutubeDLHandler(urllib2.HTTPHandler):
        """Handler for HTTP requests and responses.
 
@@ -319,11 +385,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.
        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:
 
        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.
        """
        Andrew Rowls, the author of that code, agreed to release it to the
        public domain.
        """
@@ -334,7 +400,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
                        return zlib.decompress(data, -zlib.MAX_WBITS)
                except zlib.error:
                        return zlib.decompress(data)
                        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'):
        @staticmethod
        def addinfourl_wrapper(stream, headers, url, code):
                if hasattr(urllib2.addinfourl, 'getcode'):
@@ -342,7 +408,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
                ret = urllib2.addinfourl(stream, headers, url)
                ret.code = code
                return ret
                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:
        def http_request(self, req):
                for h in std_headers:
                        if h in req.headers:
@@ -368,6 +434,7 @@ class YoutubeDLHandler(urllib2.HTTPHandler):
                        resp.msg = old_resp.msg
                return resp
 
                        resp.msg = old_resp.msg
                return resp
 
+
 class FileDownloader(object):
        """File Downloader class.
 
 class FileDownloader(object):
        """File Downloader class.
 
@@ -416,12 +483,16 @@ class FileDownloader(object):
        noprogress:       Do not print the progress bar.
        playliststart:    Playlist item to start at.
        playlistend:      Playlist item to end at.
        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
        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
+       writesubtitles:   Write the video subtitles to a .srt file
+       subtitleslang:    Language of the subtitles to download
        """
 
        params = None
        """
 
        params = None
@@ -440,16 +511,6 @@ class FileDownloader(object):
                self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
                self.params = params
 
                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:
        @staticmethod
        def format_bytes(bytes):
                if bytes is None:
@@ -461,7 +522,7 @@ class FileDownloader(object):
                else:
                        exponent = long(math.log(bytes, 1024.0))
                suffix = 'bkMGTPEZY'[exponent]
                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
                return '%.2f%s' % (converted, suffix)
 
        @staticmethod
@@ -524,16 +585,17 @@ class FileDownloader(object):
                self._pps.append(pp)
                pp.set_downloader(self)
 
                self._pps.append(pp)
                pp.set_downloader(self)
 
-       def to_screen(self, message, skip_eol=False, ignore_encoding_errors=False):
+       def to_screen(self, message, skip_eol=False):
                """Print message to stdout if not in quiet mode."""
                """Print message to stdout if not in quiet mode."""
-               try:
-                       if not self.params.get('quiet', False):
-                               terminator = [u'\n', u''][skip_eol]
-                               print >>self._screen_file, (u'%s%s' % (message, terminator)).encode(preferredencoding()),
+               assert type(message) == type(u'')
+               if not self.params.get('quiet', False):
+                       terminator = [u'\n', u''][skip_eol]
+                       output = message + terminator
+
+                       if 'b' not in self._screen_file.mode or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr
+                               output = output.encode(preferredencoding(), 'ignore')
+                       self._screen_file.write(output)
                        self._screen_file.flush()
                        self._screen_file.flush()
-               except (UnicodeEncodeError), err:
-                       if not ignore_encoding_errors:
-                               raise
 
        def to_stderr(self, message):
                """Print message to stderr."""
 
        def to_stderr(self, message):
                """Print message to stderr."""
@@ -583,7 +645,7 @@ class FileDownloader(object):
        def temp_name(self, filename):
                """Returns a temporary filename for the given filename."""
                if self.params.get('nopart', False) or filename == u'-' or \
        def temp_name(self, filename):
                """Returns a temporary filename for the given filename."""
                if self.params.get('nopart', False) or filename == u'-' or \
-                               (os.path.exists(filename) and not os.path.isfile(filename)):
+                               (os.path.exists(_encodeFilename(filename)) and not os.path.isfile(_encodeFilename(filename))):
                        return filename
                return filename + u'.part'
 
                        return filename
                return filename + u'.part'
 
@@ -596,38 +658,43 @@ class FileDownloader(object):
                try:
                        if old_filename == new_filename:
                                return
                try:
                        if old_filename == new_filename:
                                return
-                       os.rename(old_filename, new_filename)
+                       os.rename(_encodeFilename(old_filename), _encodeFilename(new_filename))
                except (IOError, OSError), err:
                        self.trouble(u'ERROR: unable to rename file')
                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:
                        return
        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:
                        return
-               if not os.path.isfile(filename):
+               if not os.path.isfile(_encodeFilename(filename)):
                        return
                timestr = last_modified_hdr
                if timestr is None:
                        return
                filetime = timeconvert(timestr)
                if filetime is None:
                        return
                timestr = last_modified_hdr
                if timestr is None:
                        return
                filetime = timeconvert(timestr)
                if filetime is None:
-                       return
+                       return filetime
                try:
                try:
-                       os.utime(filename,(time.time(), filetime))
+                       os.utime(filename, (time.time(), filetime))
                except:
                        pass
                except:
                        pass
+               return filetime
 
        def report_writedescription(self, descfn):
                """ Report that the description file is being written """
 
        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)
+               self.to_screen(u'[info] Writing video description to: ' + descfn)
+
+       def report_writesubtitles(self, srtfn):
+               """ Report that the subtitles file is being written """
+               self.to_screen(u'[info] Writing video subtitles to: ' + srtfn)
 
        def report_writeinfojson(self, infofn):
                """ Report that the metadata file has been written """
 
        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)
+               self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
 
        def report_destination(self, filename):
                """Report destination filename."""
 
        def report_destination(self, filename):
                """Report destination filename."""
-               self.to_screen(u'[download] Destination: %s' % filename, ignore_encoding_errors=True)
+               self.to_screen(u'[download] Destination: ' + filename)
 
        def report_progress(self, percent_str, data_len_str, speed_str, eta_str):
                """Report download progress."""
 
        def report_progress(self, percent_str, data_len_str, speed_str, eta_str):
                """Report download progress."""
@@ -680,52 +747,92 @@ class FileDownloader(object):
                        self.trouble(u'ERROR: invalid system charset or erroneous output template')
                        return None
 
                        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."""
        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)
                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):
                # 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
                        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:
 
                try:
-                       self.pmkdir(filename)
+                       dn = os.path.dirname(_encodeFilename(filename))
+                       if dn != '' and not os.path.exists(dn): # dn is already encoded
+                               os.makedirs(dn)
                except (OSError, IOError), err:
                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
 
                if self.params.get('writedescription', False):
                        try:
                        return
 
                if self.params.get('writedescription', False):
                        try:
-                               descfn = filename + '.description'
+                               descfn = filename + u'.description'
                                self.report_writedescription(descfn)
                                self.report_writedescription(descfn)
-                               descfile = open(descfn, 'wb')
+                               descfile = open(_encodeFilename(descfn), 'wb')
                                try:
                                        descfile.write(info_dict['description'].encode('utf-8'))
                                finally:
                                        descfile.close()
                        except (OSError, IOError):
                                try:
                                        descfile.write(info_dict['description'].encode('utf-8'))
                                finally:
                                        descfile.close()
                        except (OSError, IOError):
-                               self.trouble(u'ERROR: Cannot write description file: %s' % str(descfn))
+                               self.trouble(u'ERROR: Cannot write description file ' + descfn)
+                               return
+                               
+               if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
+                       # subtitles download errors are already managed as troubles in relevant IE
+                       # that way it will silently go on when used with unsupporting IE 
+                       try:
+                               srtfn = filename.rsplit('.', 1)[0] + u'.srt'
+                               self.report_writesubtitles(srtfn)
+                               srtfile = open(_encodeFilename(srtfn), 'wb')
+                               try:
+                                       srtfile.write(info_dict['subtitles'].encode('utf-8'))
+                               finally:
+                                       srtfile.close()
+                       except (OSError, IOError):
+                               self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
                                return
 
                if self.params.get('writeinfojson', False):
                                return
 
                if self.params.get('writeinfojson', False):
-                       infofn = filename + '.info.json'
+                       infofn = filename + u'.info.json'
                        self.report_writeinfojson(infofn)
                        try:
                                json.dump
                        self.report_writeinfojson(infofn)
                        try:
                                json.dump
@@ -733,32 +840,37 @@ class FileDownloader(object):
                                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.trouble(u'ERROR: No JSON encoder found. Update to Python 2.6+, setup a json module, or leave out --write-info-json.')
                                return
                        try:
-                               infof = open(infofn, 'wb')
+                               infof = open(_encodeFilename(infofn), 'wb')
                                try:
                                try:
-                                       json.dump(info_dict, infof)
+                                       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):
                                finally:
                                        infof.close()
                        except (OSError, IOError):
-                               self.trouble(u'ERROR: Cannot write metadata to JSON file: %s' % str(infofn))
+                               self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
                                return
 
                                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 success:
-                       try:
-                               self.post_process(filename, info_dict)
-                       except (PostProcessingError), err:
-                               self.trouble(u'ERROR: postprocessing: %s' % str(err))
-                               return
+               if not self.params.get('skip_download', False):
+                       if self.params.get('nooverwrites', False) and os.path.exists(_encodeFilename(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."""
 
        def download(self, url_list):
                """Download a given list of URLs."""
@@ -810,26 +922,42 @@ class FileDownloader(object):
                # the connection was interrumpted and resuming appears to be
                # possible. This is part of rtmpdump's normal usage, AFAIK.
                basic_args = ['rtmpdump', '-q'] + [[], ['-W', player_url]][player_url is not None] + ['-r', url, '-o', tmpfilename]
                # the connection was interrumpted and resuming appears to be
                # possible. This is part of rtmpdump's normal usage, AFAIK.
                basic_args = ['rtmpdump', '-q'] + [[], ['-W', player_url]][player_url is not None] + ['-r', url, '-o', tmpfilename]
-               retval = subprocess.call(basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)])
+               args = basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)]
+               if self.params.get('verbose', False):
+                       try:
+                               import pipes
+                               shell_quote = lambda args: ' '.join(map(pipes.quote, args))
+                       except ImportError:
+                               shell_quote = repr
+                       self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(args))
+               retval = subprocess.call(args)
                while retval == 2 or retval == 1:
                while retval == 2 or retval == 1:
-                       prevsize = os.path.getsize(tmpfilename)
+                       prevsize = os.path.getsize(_encodeFilename(tmpfilename))
                        self.to_screen(u'\r[rtmpdump] %s bytes' % prevsize, skip_eol=True)
                        time.sleep(5.0) # This seems to be needed
                        retval = subprocess.call(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
                        self.to_screen(u'\r[rtmpdump] %s bytes' % prevsize, skip_eol=True)
                        time.sleep(5.0) # This seems to be needed
                        retval = subprocess.call(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
-                       cursize = os.path.getsize(tmpfilename)
+                       cursize = os.path.getsize(_encodeFilename(tmpfilename))
                        if prevsize == cursize and retval == 1:
                                break
                        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:
                if retval == 0:
-                       self.to_screen(u'\r[rtmpdump] %s bytes' % os.path.getsize(tmpfilename))
+                       self.to_screen(u'\r[rtmpdump] %s bytes' % os.path.getsize(_encodeFilename(tmpfilename)))
                        self.try_rename(tmpfilename, filename)
                        return True
                else:
                        self.trouble(u'\nERROR: rtmpdump exited with code %d' % retval)
                        return False
 
                        self.try_rename(tmpfilename, filename)
                        return True
                else:
                        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
                # Check file already present
-               if self.params.get('continuedl', False) and os.path.isfile(filename) and not self.params.get('nopart', False):
+               if self.params.get('continuedl', False) and os.path.isfile(_encodeFilename(filename)) and not self.params.get('nopart', False):
                        self.report_file_already_downloaded(filename)
                        return True
 
                        self.report_file_already_downloaded(filename)
                        return True
 
@@ -839,7 +967,6 @@ class FileDownloader(object):
 
                tmpfilename = self.temp_name(filename)
                stream = None
 
                tmpfilename = self.temp_name(filename)
                stream = None
-               open_mode = 'wb'
 
                # Do not include the Accept-Encoding header
                headers = {'Youtubedl-no-compression': 'True'}
 
                # Do not include the Accept-Encoding header
                headers = {'Youtubedl-no-compression': 'True'}
@@ -847,22 +974,27 @@ class FileDownloader(object):
                request = urllib2.Request(url, None, headers)
 
                # Establish possible resume length
                request = urllib2.Request(url, None, headers)
 
                # Establish possible resume length
-               if os.path.isfile(tmpfilename):
-                       resume_len = os.path.getsize(tmpfilename)
+               if os.path.isfile(_encodeFilename(tmpfilename)):
+                       resume_len = os.path.getsize(_encodeFilename(tmpfilename))
                else:
                        resume_len = 0
 
                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:
 
                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:
                                data = urllib2.urlopen(request)
                                break
                        except (urllib2.HTTPError, ), err:
@@ -881,7 +1013,7 @@ class FileDownloader(object):
                                        else:
                                                # Examine the reported length
                                                if (content_length is not None and
                                        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,
                                                        # 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,
@@ -926,6 +1058,7 @@ class FileDownloader(object):
                        if stream is None:
                                try:
                                        (stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
                        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:
                                        filename = self.undo_temp_name(tmpfilename)
                                        self.report_destination(filename)
                                except (OSError, IOError), err:
@@ -939,14 +1072,20 @@ class FileDownloader(object):
                        block_size = self.best_block_size(after - before, len(data_block))
 
                        # Progress message
                        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)
                        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)
 
 
                        # 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:
                stream.close()
                self.report_finish()
                if data_len is not None and byte_counter != data_len:
@@ -955,10 +1094,11 @@ class FileDownloader(object):
 
                # Update file modification time
                if self.params.get('updatetime', True):
 
                # 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
 
 
                return True
 
+
 class InfoExtractor(object):
        """Information Extractor class.
 
 class InfoExtractor(object):
        """Information Extractor class.
 
@@ -989,9 +1129,8 @@ class InfoExtractor(object):
        description:    One-line video description.
 
        Subclasses of this one should re-define the _real_initialize() and
        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
        """
 
        _ready = False
@@ -1002,10 +1141,9 @@ class InfoExtractor(object):
                self._ready = False
                self.set_downloader(downloader)
 
                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."""
                """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)."""
 
        def initialize(self):
                """Initializes an instance (authentication, etc)."""
@@ -1030,16 +1168,18 @@ class InfoExtractor(object):
                """Real extraction process. Redefine in subclasses."""
                pass
 
                """Real extraction process. Redefine in subclasses."""
                pass
 
+
 class YoutubeIE(InfoExtractor):
        """Information extractor for youtube.com."""
 
 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
        _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',
        _video_extensions = {
                '13': '3gp',
                '17': 'mp4',
@@ -1048,12 +1188,25 @@ class YoutubeIE(InfoExtractor):
                '37': 'mp4',
                '38': 'video', # You actually don't know if this will be MOV, AVI or whatever
                '43': 'webm',
                '37': 'mp4',
                '38': 'video', # You actually don't know if this will be MOV, AVI or whatever
                '43': 'webm',
+               '44': 'webm',
                '45': '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."""
 
        def report_lang(self):
                """Report attempt to set language."""
@@ -1075,6 +1228,10 @@ class YoutubeIE(InfoExtractor):
                """Report attempt to download video info webpage."""
                self._downloader.to_screen(u'[youtube] %s: Downloading video info webpage' % video_id)
 
                """Report attempt to download video info webpage."""
                self._downloader.to_screen(u'[youtube] %s: Downloading video info webpage' % video_id)
 
+       def report_video_subtitles_download(self, video_id):
+               """Report attempt to download video info webpage."""
+               self._downloader.to_screen(u'[youtube] %s: Downloading video subtitles' % video_id)
+
        def report_information_extraction(self, video_id):
                """Report attempt to extract video information."""
                self._downloader.to_screen(u'[youtube] %s: Extracting video information' % video_id)
        def report_information_extraction(self, video_id):
                """Report attempt to extract video information."""
                self._downloader.to_screen(u'[youtube] %s: Extracting video information' % video_id)
@@ -1087,6 +1244,28 @@ class YoutubeIE(InfoExtractor):
                """Indicate the download will use the RTMP protocol."""
                self._downloader.to_screen(u'[youtube] RTMP download detected')
 
                """Indicate the download will use the RTMP protocol."""
                self._downloader.to_screen(u'[youtube] RTMP download detected')
 
+       def _closed_captions_xml_to_srt(self, xml_string):
+               srt = ''
+               texts = re.findall(r'<text start="([\d\.]+)"( dur="([\d\.]+)")?>([^<]+)</text>', xml_string, re.MULTILINE)
+               # TODO parse xml instead of regex
+               for n, (start, dur_tag, dur, caption) in enumerate(texts):
+                       if not dur: dur = '4'
+                       start = float(start)
+                       end = start + float(dur)
+                       start = "%02i:%02i:%02i,%03i" %(start/(60*60), start/60%60, start%60, start%1*1000)
+                       end = "%02i:%02i:%02i,%03i" %(end/(60*60), end/60%60, end%60, end%1*1000)
+                       caption = re.sub(ur'(?u)&(.+?);', htmlentity_transform, caption)
+                       caption = re.sub(ur'(?u)&(.+?);', htmlentity_transform, caption) # double cycle, inentional
+                       srt += str(n) + '\n'
+                       srt += start + ' --> ' + end + '\n'
+                       srt += caption + '\n\n'
+               return srt
+
+       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
        def _real_initialize(self):
                if self._downloader is None:
                        return
@@ -1166,7 +1345,7 @@ class YoutubeIE(InfoExtractor):
 
                # Get video webpage
                self.report_video_webpage_download(video_id)
 
                # Get video webpage
                self.report_video_webpage_download(video_id)
-               request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&amp;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:
                try:
                        video_webpage = urllib2.urlopen(request).read()
                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
@@ -1184,7 +1363,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'
                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()
                        request = urllib2.Request(video_info_url)
                        try:
                                video_info_webpage = urllib2.urlopen(request).read()
@@ -1219,8 +1398,7 @@ class YoutubeIE(InfoExtractor):
                video_title = sanitize_title(video_title)
 
                # simplified title
                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:
 
                # thumbnail image
                if 'thumbnail_url' not in video_info:
@@ -1246,15 +1424,45 @@ class YoutubeIE(InfoExtractor):
                        lxml.etree
                except NameError:
                        video_description = u'No description available.'
                        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'<meta name="description" content="(.*)"(?:\s*/)?>', video_webpage)
-                               if mobj is not None:
-                                       video_description = mobj.group(1).decode('utf-8')
+                       mobj = re.search(r'<meta name="description" content="(.*?)">', 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
                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
+                       
+               # closed captions
+               video_subtitles = None
+               if self._downloader.params.get('writesubtitles', False):
+                       self.report_video_subtitles_download(video_id)
+                       request = urllib2.Request('http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id)
+                       try:
+                               srt_list = urllib2.urlopen(request).read()
+                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                               self._downloader.trouble(u'WARNING: unable to download video subtitles: %s' % str(err))
+                       else:
+                               srt_lang_list = re.findall(r'lang_code="([\w\-]+)"', srt_list)
+                               if srt_lang_list:
+                                       if self._downloader.params.get('subtitleslang', False):
+                                               srt_lang = self._downloader.params.get('subtitleslang')
+                                       elif 'en' in srt_lang_list:
+                                               srt_lang = 'en'
+                                       else:
+                                               srt_lang = srt_lang_list[0]
+                                       if not srt_lang in srt_lang_list:
+                                               self._downloader.trouble(u'WARNING: no closed captions found in the specified language')
+                                       else:
+                                               request = urllib2.Request('http://video.google.com/timedtext?hl=en&lang=%s&v=%s' % (srt_lang, video_id))
+                                               try:
+                                                       srt_xml = urllib2.urlopen(request).read()
+                                               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                                                       self._downloader.trouble(u'WARNING: unable to download video subtitles: %s' % str(err))
+                                               else:
+                                                       video_subtitles = self._closed_captions_xml_to_srt(srt_xml.decode('utf-8'))
+                               else:
+                                       self._downloader.trouble(u'WARNING: video has no closed captions')
 
                # token
                video_token = urllib.unquote_plus(video_info['token'][0])
 
                # token
                video_token = urllib.unquote_plus(video_info['token'][0])
@@ -1272,24 +1480,36 @@ class YoutubeIE(InfoExtractor):
                        url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
 
                        format_limit = self._downloader.params.get('format_limit', None)
                        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:
                        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
                        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
                                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:
                                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
                                        self._downloader.trouble(u'ERROR: requested format not available')
                                        return
-                               video_url_list = [(req_format, url_map[req_format])] # Specific format
                else:
                        self._downloader.trouble(u'ERROR: no conn or url_encoded_fmt_stream_map information found in video info')
                        return
                else:
                        self._downloader.trouble(u'ERROR: no conn or url_encoded_fmt_stream_map information found in video info')
                        return
@@ -1315,6 +1535,7 @@ class YoutubeIE(InfoExtractor):
                                        'thumbnail':    video_thumbnail.decode('utf-8'),
                                        'description':  video_description,
                                        'player_url':   player_url,
                                        'thumbnail':    video_thumbnail.decode('utf-8'),
                                        'description':  video_description,
                                        'player_url':   player_url,
+                                       'subtitles':    video_subtitles
                                })
                        except UnavailableVideoError, err:
                                self._downloader.trouble(u'\nERROR: unable to download video')
                                })
                        except UnavailableVideoError, err:
                                self._downloader.trouble(u'\nERROR: unable to download video')
@@ -1327,15 +1548,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
        _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
 
 
        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')
        def report_disclaimer(self):
                """Report disclaimer retrieval."""
                self._downloader.to_screen(u'[metacafe] Retrieving disclaimer')
@@ -1469,14 +1687,11 @@ class DailymotionIE(InfoExtractor):
        """Information Extractor for Dailymotion"""
 
        _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)'
        """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)
 
 
        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)
        def report_download_webpage(self, video_id):
                """Report webpage download."""
                self._downloader.to_screen(u'[dailymotion] %s: Downloading webpage' % video_id)
@@ -1485,9 +1700,6 @@ class DailymotionIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
 
                """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)
        def _real_extract(self, url):
                # Extract id and simplified title from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1499,11 +1711,11 @@ class DailymotionIE(InfoExtractor):
                self._downloader.increment_downloads()
                video_id = mobj.group(1)
 
                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)
                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()
                try:
                        self.report_download_webpage(video_id)
                        webpage = urllib2.urlopen(request).read()
@@ -1513,25 +1725,30 @@ class DailymotionIE(InfoExtractor):
 
                # Extract URL, uploader and title from webpage
                self.report_extraction(video_id)
 
                # 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
                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
 
 
                # if needed add http://www.dailymotion.com/ if relative URL
 
                video_url = mediaURL
 
-               # '<meta\s+name="title"\s+content="Dailymotion\s*[:\-]\s*(.*?)"\s*\/\s*>'
-               mobj = re.search(r'(?im)<title>Dailymotion\s*[\-:]\s*(.+?)</title>', webpage)
+               mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract title')
                        return
                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)
                video_title = sanitize_title(video_title)
+               simple_title = _simplify_title(video_title)
 
 
-               mobj = re.search(r'(?im)<Attribute name="owner">(.+?)</Attribute>', webpage)
+               mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a></span>', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
                        return
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
                        return
@@ -1553,18 +1770,16 @@ class DailymotionIE(InfoExtractor):
                except UnavailableVideoError:
                        self._downloader.trouble(u'\nERROR: unable to download video')
 
                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=([^\&]+).*'
 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)
 
 
        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)
        def report_download_webpage(self, video_id):
                """Report webpage download."""
                self._downloader.to_screen(u'[video.google] %s: Downloading webpage' % video_id)
@@ -1573,9 +1788,6 @@ class GoogleIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[video.google] %s: Extracting information' % video_id)
 
                """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)
        def _real_extract(self, url):
                # Extract id from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1619,7 +1831,7 @@ class GoogleIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
                        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'<span id=short-desc-content>([^<]*)</span>', webpage)
 
                # Extract video description
                mobj = re.search(r'<span id=short-desc-content>([^<]*)</span>', webpage)
@@ -1646,7 +1858,6 @@ class GoogleIE(InfoExtractor):
                else:   # we need something to pass to process_info
                        video_thumbnail = ''
 
                else:   # we need something to pass to process_info
                        video_thumbnail = ''
 
-
                try:
                        # Process video information
                        self._downloader.process_info({
                try:
                        # Process video information
                        self._downloader.process_info({
@@ -1668,14 +1879,11 @@ class PhotobucketIE(InfoExtractor):
        """Information extractor for photobucket.com."""
 
        _VALID_URL = r'(?:http://)?(?:[a-z0-9]+\.)?photobucket\.com/.*[\?\&]current=(.*\.flv)'
        """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)
 
 
        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)
        def report_download_webpage(self, video_id):
                """Report webpage download."""
                self._downloader.to_screen(u'[photobucket] %s: Downloading webpage' % video_id)
@@ -1684,9 +1892,6 @@ class PhotobucketIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[photobucket] %s: Extracting information' % video_id)
 
                """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)
        def _real_extract(self, url):
                # Extract id from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1725,7 +1930,7 @@ class PhotobucketIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
                        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')
 
 
                video_uploader = mobj.group(2).decode('utf-8')
 
@@ -1753,14 +1958,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]+)(?:[#\?].*)?'
        # _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)
 
 
        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)
        def report_download_webpage(self, video_id):
                """Report webpage download."""
                self._downloader.to_screen(u'[video.yahoo] %s: Downloading webpage' % video_id)
@@ -1769,9 +1971,6 @@ class YahooIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[video.yahoo] %s: Extracting information' % video_id)
 
                """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)
        def _real_extract(self, url, new_video=True):
                # Extract ID from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1825,7 +2024,7 @@ class YahooIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: unable to extract video title')
                        return
                video_title = mobj.group(1).decode('utf-8')
                        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'<h2 class="ti-5"><a href="http://video\.yahoo\.com/(people|profile)/[0-9]+" beacon=".*">(.*)</a></h2>', webpage)
                if mobj is None:
 
                mobj = re.search(r'<h2 class="ti-5"><a href="http://video\.yahoo\.com/(people|profile)/[0-9]+" beacon=".*">(.*)</a></h2>', webpage)
                if mobj is None:
@@ -1846,7 +2045,8 @@ class YahooIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: unable to extract video description')
                        return
                video_description = mobj.group(1).decode('utf-8')
                        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'<meta name="video_height" content="([0-9]+)" />', webpage)
 
                # Extract video height and width
                mobj = re.search(r'<meta name="video_height" content="([0-9]+)" />', webpage)
@@ -1867,8 +2067,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 +
                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()
                try:
                        self.report_download_webpage(video_id)
                        webpage = urllib2.urlopen(request).read()
@@ -1897,23 +2097,141 @@ class YahooIE(InfoExtractor):
                                'thumbnail':    video_thumbnail.decode('utf-8'),
                                'description':  video_description,
                                'thumbnail':    video_thumbnail,
                                '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')
 
 
                                '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(url, 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 the config JSON
+               config = webpage.split(' = {config:')[1].split(',assets:')[0]
+               try:
+                       config = json.loads(config)
+               except:
+                       self._downloader.trouble(u'ERROR: unable to extract info section')
+                       return
+               
+               # Extract title
+               video_title = config["video"]["title"]
+               simple_title = _simplify_title(video_title)
+
+               # Extract uploader
+               video_uploader = config["video"]["owner"]["name"]
+
+               # Extract video thumbnail
+               video_thumbnail = config["video"]["thumbnail"]
+
+               # Extract video description
+               try:
+                       lxml.etree
+               except NameError:
+                       video_description = u'No description available.'
+                       mobj = re.search(r'<meta name="description" content="(.*?)" />', webpage, re.MULTILINE)
+                       if mobj is not None:
+                               video_description = mobj.group(1)
+               else:
+                       html_parser = lxml.etree.HTMLParser()
+                       vwebpage_doc = lxml.etree.parse(StringIO.StringIO(webpage), html_parser)
+                       video_description = u''.join(vwebpage_doc.xpath('id("description")//text()')).strip()
+                       # TODO use another parser
+
+               # Extract upload date
+               video_upload_date = u'NA'
+               mobj = re.search(r'<span id="clip-date" style="display:none">[^:]*: (.*?)( \([^\(]*\))?</span>', webpage)
+               if mobj is not None:
+                       video_upload_date = mobj.group(1)
+
+               # Vimeo specific: extract request signature and timestamp
+               sig = config['request']['signature']
+               timestamp = config['request']['timestamp']
+
+               # Vimeo specific: extract video codec and quality information
+               # 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'
+                               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())
+
+               try:
+                       # Process video information
+                       self._downloader.process_info({
+                               'id':           video_id,
+                               'url':          video_url,
+                               'uploader':     video_uploader,
+                               'upload_date':  video_upload_date,
+                               'title':        video_title,
+                               'stitle':       simple_title,
+                               'ext':          video_extension,
+                               '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."""
 
 class GenericIE(InfoExtractor):
        """Generic last-resort information extractor."""
 
+       _VALID_URL = r'.*'
+       IE_NAME = u'generic'
+
        def __init__(self, downloader=None):
                InfoExtractor.__init__(self, downloader)
 
        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.')
        def report_download_webpage(self, video_id):
                """Report webpage download."""
                self._downloader.to_screen(u'WARNING: Falling back on generic information extractor.')
@@ -1923,9 +2241,6 @@ class GenericIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[generic] %s: Extracting information' % video_id)
 
                """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()
        def _real_extract(self, url):
                # At this point we have a new video
                self._downloader.increment_downloads()
@@ -1961,11 +2276,11 @@ class GenericIE(InfoExtractor):
                        return
 
                video_url = urllib.unquote(mobj.group(1))
                        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:]
 
                # 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
 
                # it's tempting to parse this further, but you would
                # have to take into account all the variations like
@@ -1979,7 +2294,7 @@ class GenericIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
                        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)
 
                # video uploader is domain name
                mobj = re.match(r'(?:https?://)?([^/]*)/.*', url)
@@ -2007,21 +2322,16 @@ class GenericIE(InfoExtractor):
 
 class YoutubeSearchIE(InfoExtractor):
        """Information Extractor for YouTube search queries."""
 
 class YoutubeSearchIE(InfoExtractor):
        """Information Extractor for YouTube search queries."""
-       _VALID_QUERY = 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*</a>'
+       _VALID_URL = r'ytsearch(\d+|all)?:[\s\S]+'
+       _API_URL = 'https://gdata.youtube.com/feeds/api/videos?q=%s&start-index=%i&max-results=50&v=2&alt=jsonc'
        _youtube_ie = None
        _max_youtube_results = 1000
        _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
 
 
        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())
        def report_download_page(self, query, pagenum):
                """Report attempt to download playlist page with given number."""
                query = query.decode(preferredencoding())
@@ -2031,14 +2341,14 @@ class YoutubeSearchIE(InfoExtractor):
                self._youtube_ie.initialize()
 
        def _real_extract(self, query):
                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:]
                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
                if prefix == '':
                        self._download_n_results(query, 1)
                        return
@@ -2052,7 +2362,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.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
                                        n = self._max_youtube_results
                                self._download_n_results(query, n)
                                return
@@ -2064,55 +2374,47 @@ class YoutubeSearchIE(InfoExtractor):
                """Downloads a specified number of results for a query"""
 
                video_ids = []
                """Downloads a specified number of results for a query"""
 
                video_ids = []
-               already_seen = set()
-               pagenum = 1
+               pagenum = 0
+               limit = n
 
 
-               while True:
-                       self.report_download_page(query, pagenum)
-                       result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
+               while (50 * pagenum) < limit:
+                       self.report_download_page(query, pagenum+1)
+                       result_url = self._API_URL % (urllib.quote_plus(query), (50*pagenum)+1)
                        request = urllib2.Request(result_url)
                        try:
                        request = urllib2.Request(result_url)
                        try:
-                               page = urllib2.urlopen(request).read()
+                               data = urllib2.urlopen(request).read()
                        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                        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 API page: %s' % str(err))
                                return
                                return
+                       api_response = json.loads(data)['data']
 
 
-                       # Extract video identifiers
-                       for mobj in re.finditer(self._VIDEO_INDICATOR, page):
-                               video_id = page[mobj.span()[0]:mobj.span()[1]].split('=')[2][:-1]
-                               if video_id not in already_seen:
-                                       video_ids.append(video_id)
-                                       already_seen.add(video_id)
-                                       if len(video_ids) == n:
-                                               # Specified n videos reached
-                                               for id in video_ids:
-                                                       self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
-                                               return
+                       new_ids = list(video['id'] for video in api_response['items'])
+                       video_ids += new_ids
 
 
-                       if re.search(self._MORE_PAGES_INDICATOR, page) is None:
-                               for id in video_ids:
-                                       self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
-                               return
+                       limit = min(n, api_response['totalItems'])
+                       pagenum += 1
+
+               if len(video_ids) > n:
+                       video_ids = video_ids[:n]
+               for id in video_ids:
+                       self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
+               return
 
 
-                       pagenum = pagenum + 1
 
 class GoogleSearchIE(InfoExtractor):
        """Information Extractor for Google Video search queries."""
 
 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'
        _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'<span>Next</span>'
+       _VIDEO_INDICATOR = r'<a href="http://video\.google\.com/videoplay\?docid=([^"\&]+)'
+       _MORE_PAGES_INDICATOR = r'class="pn" id="pnnext"'
        _google_ie = None
        _max_google_results = 1000
        _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
 
 
        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())
        def report_download_page(self, query, pagenum):
                """Report attempt to download playlist page with given number."""
                query = query.decode(preferredencoding())
@@ -2122,14 +2424,14 @@ class GoogleSearchIE(InfoExtractor):
                self._google_ie.initialize()
 
        def _real_extract(self, query):
                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:]
                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
                if prefix == '':
                        self._download_n_results(query, 1)
                        return
@@ -2143,7 +2445,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.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
                                        n = self._max_google_results
                                self._download_n_results(query, n)
                                return
@@ -2155,12 +2457,11 @@ class GoogleSearchIE(InfoExtractor):
                """Downloads a specified number of results for a query"""
 
                video_ids = []
                """Downloads a specified number of results for a query"""
 
                video_ids = []
-               already_seen = set()
-               pagenum = 1
+               pagenum = 0
 
                while True:
                        self.report_download_page(query, pagenum)
 
                while True:
                        self.report_download_page(query, pagenum)
-                       result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
+                       result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum*10)
                        request = urllib2.Request(result_url)
                        try:
                                page = urllib2.urlopen(request).read()
                        request = urllib2.Request(result_url)
                        try:
                                page = urllib2.urlopen(request).read()
@@ -2171,9 +2472,8 @@ class GoogleSearchIE(InfoExtractor):
                        # Extract video identifiers
                        for mobj in re.finditer(self._VIDEO_INDICATOR, page):
                                video_id = mobj.group(1)
                        # Extract video identifiers
                        for mobj in re.finditer(self._VIDEO_INDICATOR, page):
                                video_id = mobj.group(1)
-                               if video_id not in already_seen:
+                               if video_id not in video_ids:
                                        video_ids.append(video_id)
                                        video_ids.append(video_id)
-                                       already_seen.add(video_id)
                                        if len(video_ids) == n:
                                                # Specified n videos reached
                                                for id in video_ids:
                                        if len(video_ids) == n:
                                                # Specified n videos reached
                                                for id in video_ids:
@@ -2187,23 +2487,21 @@ class GoogleSearchIE(InfoExtractor):
 
                        pagenum = pagenum + 1
 
 
                        pagenum = pagenum + 1
 
+
 class YahooSearchIE(InfoExtractor):
        """Information Extractor for Yahoo! Video search queries."""
 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
        _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
 
 
        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())
        def report_download_page(self, query, pagenum):
                """Report attempt to download playlist page with given number."""
                query = query.decode(preferredencoding())
@@ -2213,14 +2511,14 @@ class YahooSearchIE(InfoExtractor):
                self._yahoo_ie.initialize()
 
        def _real_extract(self, query):
                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:]
                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
                if prefix == '':
                        self._download_n_results(query, 1)
                        return
@@ -2234,7 +2532,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.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
                                        n = self._max_yahoo_results
                                self._download_n_results(query, n)
                                return
@@ -2278,23 +2576,21 @@ class YahooSearchIE(InfoExtractor):
 
                        pagenum = pagenum + 1
 
 
                        pagenum = pagenum + 1
 
+
 class YoutubePlaylistIE(InfoExtractor):
        """Information Extractor for YouTube playlists."""
 
 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'
        _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
-       _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
+       _VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=PL%s&'
        _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
        _youtube_ie = None
        _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
        _youtube_ie = None
+       IE_NAME = u'youtube:playlist'
 
        def __init__(self, youtube_ie, downloader=None):
                InfoExtractor.__init__(self, downloader)
                self._youtube_ie = youtube_ie
 
 
        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))
        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))
@@ -2328,7 +2624,8 @@ class YoutubePlaylistIE(InfoExtractor):
 
                while True:
                        self.report_download_page(playlist_id, pagenum)
 
                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:
                        try:
                                page = urllib2.urlopen(request).read()
                        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
@@ -2337,7 +2634,7 @@ class YoutubePlaylistIE(InfoExtractor):
 
                        # Extract video identifiers
                        ids_in_page = []
 
                        # Extract video identifiers
                        ids_in_page = []
-                       for mobj in re.finditer(self._VIDEO_INDICATOR, page):
+                       for mobj in re.finditer(self._VIDEO_INDICATOR_TEMPLATE % playlist_id, page):
                                if mobj.group(1) not in ids_in_page:
                                        ids_in_page.append(mobj.group(1))
                        video_ids.extend(ids_in_page)
                                if mobj.group(1) not in ids_in_page:
                                        ids_in_page.append(mobj.group(1))
                        video_ids.extend(ids_in_page)
@@ -2348,34 +2645,35 @@ class YoutubePlaylistIE(InfoExtractor):
 
                playliststart = self._downloader.params.get('playliststart', 1) - 1
                playlistend = self._downloader.params.get('playlistend', -1)
 
                playliststart = self._downloader.params.get('playliststart', 1) - 1
                playlistend = self._downloader.params.get('playlistend', -1)
-               video_ids = video_ids[playliststart:playlistend]
+               if playlistend == -1:
+                       video_ids = video_ids[playliststart:]
+               else:
+                       video_ids = video_ids[playliststart:playlistend]
 
                for id in video_ids:
                        self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
                return
 
 
                for id in video_ids:
                        self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
                return
 
+
 class YoutubeUserIE(InfoExtractor):
        """Information Extractor for YouTube users."""
 
 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'
        _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
        _youtube_ie = None
+       IE_NAME = u'youtube:user'
 
        def __init__(self, youtube_ie, downloader=None):
                InfoExtractor.__init__(self, downloader)
                self._youtube_ie = youtube_ie
 
 
        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' %
        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()
 
        def _real_initialize(self):
                self._youtube_ie.initialize()
@@ -2438,8 +2736,8 @@ class YoutubeUserIE(InfoExtractor):
                else:
                        video_ids = video_ids[playliststart:playlistend]
 
                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)))
+               self._downloader.to_screen(u"[youtube] user %s: Collected %d video ids (downloading %d of them)" %
+                               (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)
 
                for video_id in video_ids:
                        self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % video_id)
@@ -2448,15 +2746,12 @@ class YoutubeUserIE(InfoExtractor):
 class DepositFilesIE(InfoExtractor):
        """Information extractor for depositfiles.com"""
 
 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)
 
 
        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)
        def report_download_webpage(self, file_id):
                """Report webpage download."""
                self._downloader.to_screen(u'[DepositFiles] %s: Downloading webpage' % file_id)
@@ -2465,9 +2760,6 @@ class DepositFilesIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[DepositFiles] %s: Extracting information' % file_id)
 
                """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()
        def _real_extract(self, url):
                # At this point we have a new file
                self._downloader.increment_downloads()
@@ -2524,25 +2816,24 @@ class DepositFilesIE(InfoExtractor):
                except UnavailableVideoError, err:
                        self._downloader.trouble(u'ERROR: unable to download file')
 
                except UnavailableVideoError, err:
                        self._downloader.trouble(u'ERROR: unable to download file')
 
+
 class FacebookIE(InfoExtractor):
        """Information Extractor for Facebook"""
 
 class FacebookIE(InfoExtractor):
        """Information Extractor for Facebook"""
 
-       _VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook.com/video/video.php\?(?:.*?)v=(?P<ID>\d+)(?:.*)'
+       _VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook\.com/(?:video/video|photo)\.php\?(?:.*?)v=(?P<ID>\d+)(?:.*)'
        _LOGIN_URL = 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php&'
        _NETRC_MACHINE = 'facebook'
        _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_extensions = {
+               'video': 'mp4',
                'highqual': 'mp4',
                'lowqual': 'mp4',
        }
                'highqual': 'mp4',
                'lowqual': 'mp4',
        }
+       IE_NAME = u'facebook'
 
        def __init__(self, downloader=None):
                InfoExtractor.__init__(self, downloader)
 
 
        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)
        def _reporter(self, message):
                """Add header and report message."""
                self._downloader.to_screen(u'[facebook] %s' % message)
@@ -2562,10 +2853,9 @@ class FacebookIE(InfoExtractor):
        def _parse_page(self, video_webpage):
                """Extract video information from page"""
                # General data
        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'<div class="datawrap">(.*?)</div>',
                        'owner': r'\("video_owner_name", "(.*?)"\)',
                        'description': r'<div class="datawrap">(.*?)</div>',
                        'owner': r'\("video_owner_name", "(.*?)"\)',
-                       'upload_date': r'data-date="(.*?)"',
                        'thumbnail':  r'\("thumb_url", "(?P<THUMB>.*?)"\)',
                        }
                video_info = {}
                        'thumbnail':  r'\("thumb_url", "(?P<THUMB>.*?)"\)',
                        }
                video_info = {}
@@ -2667,9 +2957,7 @@ class FacebookIE(InfoExtractor):
                video_title = video_title.decode('utf-8')
                video_title = sanitize_title(video_title)
 
                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:
 
                # thumbnail image
                if 'thumbnail' not in video_info:
@@ -2708,6 +2996,8 @@ class FacebookIE(InfoExtractor):
                                return
                        if req_format is None:
                                video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
                                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:
                        elif req_format == '-1':
                                video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
                        else:
@@ -2748,19 +3038,15 @@ class BlipTVIE(InfoExtractor):
 
        _VALID_URL = r'^(?:https?://)?(?:\w+\.)?blip\.tv(/.+)$'
        _URL_EXT = r'^.*\.([a-z0-9]+)$'
 
        _VALID_URL = r'^(?:https?://)?(?:\w+\.)?blip\.tv(/.+)$'
        _URL_EXT = r'^.*\.([a-z0-9]+)$'
-
-       @staticmethod
-       def suitable(url):
-               return (re.match(BlipTVIE._VALID_URL, url) is not None)
+       IE_NAME = u'blip.tv'
 
        def report_extraction(self, file_id):
                """Report information extraction."""
 
        def report_extraction(self, file_id):
                """Report information extraction."""
-               self._downloader.to_screen(u'[blip.tv] %s: Extracting information' % file_id)
+               self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, file_id))
 
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
+       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)
 
        def _real_extract(self, url):
                mobj = re.match(self._VALID_URL, url)
@@ -2775,43 +3061,65 @@ class BlipTVIE(InfoExtractor):
                json_url = url + cchar + 'skin=json&version=2&no_wrap=1'
                request = urllib2.Request(json_url)
                self.report_extraction(mobj.group(1))
                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:
                try:
-                       json_code = urllib2.urlopen(request).read()
+                       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
                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                        self._downloader.trouble(u'ERROR: unable to download 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)
+               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
 
 
-                       self._downloader.increment_downloads()
+                       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
 
 
-                       info = {
-                               'id': data['item_id'],
-                               'url': video_url,
-                               'uploader': data['display_name'],
-                               'upload_date': upload_date,
-                               'title': data['title'],
-                               'stitle': self._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)
 
                try:
                        self._downloader.process_info(info)
@@ -2819,237 +3127,1253 @@ class BlipTVIE(InfoExtractor):
                        self._downloader.trouble(u'\nERROR: unable to download video')
 
 
                        self._downloader.trouble(u'\nERROR: unable to download video')
 
 
-class PostProcessor(object):
-       """Post Processor class.
+class MyVideoIE(InfoExtractor):
+       """Information Extractor for myvideo.de."""
 
 
-       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
-       PostProcessor.
+       _VALID_URL = r'(?:http://)?(?:www\.)?myvideo\.de/watch/([0-9]+)/([^?/]+).*'
+       IE_NAME = u'myvideo'
 
 
-       The chain will be stopped if one of them ever returns None or the end
-       of the chain is reached.
+       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)
 
 
-       PostProcessor objects follow a "mutual registration" process similar
-       to InfoExtractor objects.
-       """
+       def report_extraction(self, video_id):
+               """Report information extraction."""
+               self._downloader.to_screen(u'[myvideo] %s: Extracting information' % video_id)
 
 
-       _downloader = None
+       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
 
 
-       def __init__(self, downloader=None):
-               self._downloader = downloader
+               video_id = mobj.group(1)
 
 
-       def set_downloader(self, downloader):
-               """Sets the downloader for this PP."""
-               self._downloader = downloader
+               # 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
 
 
-       def run(self, information):
-               """Run the PostProcessor.
+               self.report_extraction(video_id)
+               mobj = re.search(r'<link rel=\'image_src\' href=\'(http://is[0-9].myvideo\.de/de/movie[0-9]+/[a-f0-9]+)/thumbs/[^.]+\.jpg\' />',
+                                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)
 
 
-               The "information" argument is a dictionary like the ones
-               composed by InfoExtractors. The only difference is that this
-               one has an extra field called "filepath" that points to the
-               downloaded file.
+               mobj = re.search('<title>([^<]+)</title>', webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract title')
+                       return
 
 
-               When this method returns None, the postprocessing chain is
-               stopped. However, this method may return an information
-               dictionary that will be passed to the next postprocessing
-               object in the chain. It can be the one it received after
-               changing some fields.
+               video_title = mobj.group(1)
+               video_title = sanitize_title(video_title)
 
 
-               In addition, this method may raise a PostProcessingError
-               exception that will be taken into account by the downloader
-               it was called from.
-               """
-               return information # by default, do nothing
+               simple_title = _simplify_title(video_title)
 
 
-class FFmpegExtractAudioPP(PostProcessor):
+               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')
 
 
-       def __init__(self, downloader=None, preferredcodec=None):
-               PostProcessor.__init__(self, downloader)
-               if preferredcodec is None:
-                       preferredcodec = 'best'
-               self._preferredcodec = preferredcodec
+class ComedyCentralIE(InfoExtractor):
+       """Information extractor for The Daily Show and Colbert Report """
 
 
-       @staticmethod
-       def get_audio_codec(path):
-               try:
-                       cmd = ['ffprobe', '-show_streams', '--', path]
-                       handle = subprocess.Popen(cmd, stderr=file(os.path.devnull, 'w'), stdout=subprocess.PIPE)
-                       output = handle.communicate()[0]
-                       if handle.wait() != 0:
-                               return None
-               except (IOError, OSError):
-                       return None
-               audio_codec = None
-               for line in output.split('\n'):
-                       if line.startswith('codec_name='):
-                               audio_codec = line.split('=')[1].strip()
-                       elif line.strip() == 'codec_type=audio' and audio_codec is not None:
-                               return audio_codec
-               return None
+       _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'
 
 
-       @staticmethod
-       def run_ffmpeg(path, out_path, codec, more_opts):
-               try:
-                       cmd = ['ffmpeg', '-y', '-i', path, '-vn', '-acodec', codec] + more_opts + ['--', out_path]
-                       ret = subprocess.call(cmd, stdout=file(os.path.devnull, 'w'), stderr=subprocess.STDOUT)
-                       return (ret == 0)
-               except (IOError, OSError):
-                       return False
+       def report_extraction(self, episode_id):
+               self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id)
 
 
-       def run(self, information):
-               path = information['filepath']
+       def report_config_download(self, episode_id):
+               self._downloader.to_screen(u'[comedycentral] %s: Downloading configuration' % episode_id)
 
 
-               filecodec = self.get_audio_codec(path)
-               if filecodec is None:
-                       self._downloader.to_stderr(u'WARNING: unable to obtain file audio codec with ffprobe')
-                       return None
+       def report_index_download(self, episode_id):
+               self._downloader.to_screen(u'[comedycentral] %s: Downloading show index' % episode_id)
 
 
-               more_opts = []
-               if self._preferredcodec == 'best' or self._preferredcodec == filecodec:
-                       if filecodec == 'aac' or filecodec == 'mp3':
-                               # Lossless if possible
-                               acodec = 'copy'
-                               extension = filecodec
-                               if filecodec == 'aac':
-                                       more_opts = ['-f', 'adts']
+       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:
                        else:
-                               # MP3 otherwise.
-                               acodec = 'libmp3lame'
-                               extension = 'mp3'
-                               more_opts = ['-ab', '128k']
+                               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:
                else:
-                       # We convert the audio (lossy)
-                       acodec = {'mp3': 'libmp3lame', 'aac': 'aac'}[self._preferredcodec]
-                       extension = self._preferredcodec
-                       more_opts = ['-ab', '128k']
-                       if self._preferredcodec == 'aac':
-                               more_opts += ['-f', 'adts']
+                       epTitle = mobj.group('episode')
 
 
-               (prefix, ext) = os.path.splitext(path)
-               new_path = prefix + '.' + extension
-               self._downloader.to_screen(u'[ffmpeg] Destination: %s' % new_path)
-               status = self.run_ffmpeg(path, new_path, acodec, more_opts)
+               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')
 
 
-               if not status:
-                       self._downloader.to_stderr(u'WARNING: error running ffmpeg')
-                       return None
+               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
 
 
+               playerUrl_raw = mMovieParams[0][0]
+               self.report_player_url(epTitle)
                try:
                try:
-                       os.remove(path)
-               except (IOError, OSError):
-                       self._downloader.to_stderr(u'WARNING: Unable to remove downloaded video file')
-                       return None
+                       urlHandle = urllib2.urlopen(playerUrl_raw)
+                       playerUrl = urlHandle.geturl()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: unable to find out player URL: ' + unicode(err))
+                       return
 
 
-               information['filepath'] = new_path
-               return information
+               uri = mMovieParams[0][1]
+               indexUrl = 'http://shadow.comedycentral.com/feeds/video_player/mrss/?' + urllib.urlencode({'uri': uri})
+               self.report_index_download(epTitle)
+               try:
+                       indexXml = urllib2.urlopen(indexUrl).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: unable to download episode index: ' + unicode(err))
+                       return
 
 
+               idoc = xml.etree.ElementTree.fromstring(indexXml)
+               itemEls = idoc.findall('.//item')
+               for itemEl in itemEls:
+                       mediaId = itemEl.findall('./guid')[0].text
+                       shortMediaId = mediaId.split(':')[-1]
+                       showId = mediaId.split(':')[-2].replace('.com', '')
+                       officialTitle = itemEl.findall('./title')[0].text
+                       officialDate = itemEl.findall('./pubDate')[0].text
+
+                       configUrl = ('http://www.comedycentral.com/global/feeds/entertainment/media/mediaGenEntertainment.jhtml?' +
+                                               urllib.urlencode({'uri': mediaId}))
+                       configReq = urllib2.Request(configUrl)
+                       self.report_config_download(epTitle)
+                       try:
+                               configXml = urllib2.urlopen(configReq).read()
+                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                               self._downloader.trouble(u'ERROR: unable to download webpage: %s' % unicode(err))
+                               return
 
 
-def updateSelf(downloader, filename):
-       ''' Update the program file with the latest version from the repository '''
-       # Note: downloader only used for options
-       if not os.access(filename, os.W_OK):
-               sys.exit('ERROR: no write permissions on %s' % filename)
+                       cdoc = xml.etree.ElementTree.fromstring(configXml)
+                       turls = []
+                       for rendition in cdoc.findall('.//rendition'):
+                               finfo = (rendition.attrib['bitrate'], rendition.findall('./src')[0].text)
+                               turls.append(finfo)
 
 
-       downloader.to_screen('Updating to latest stable version...')
+                       if len(turls) == 0:
+                               self._downloader.trouble(u'\nERROR: unable to download ' + mediaId + ': No videos found')
+                               continue
 
 
-       try:
-               latest_url = 'http://github.com/rg3/youtube-dl/raw/master/LATEST_VERSION'
-               latest_version = urllib.urlopen(latest_url).read().strip()
-               prog_url = 'http://github.com/rg3/youtube-dl/raw/%s/youtube-dl' % latest_version
-               newcontent = urllib.urlopen(prog_url).read()
-       except (IOError, OSError), err:
-               sys.exit('ERROR: unable to download latest version')
+                       # For now, just pick the highest bitrate
+                       format,video_url = turls[-1]
 
 
-       try:
-               stream = open(filename, 'w')
-               stream.write(newcontent)
-               stream.close()
-       except (IOError, OSError), err:
-               sys.exit('ERROR: unable to overwrite current version')
+                       self._downloader.increment_downloads()
 
 
-       downloader.to_screen('Updated to version %s' % latest_version)
+                       effTitle = showId + u'-' + epTitle
+                       info = {
+                               'id': shortMediaId,
+                               'url': video_url,
+                               'uploader': showId,
+                               'upload_date': officialDate,
+                               'title': effTitle,
+                               'stitle': _simplify_title(effTitle),
+                               'ext': 'mp4',
+                               'format': format,
+                               'thumbnail': None,
+                               'description': officialTitle,
+                               'player_url': playerUrl
+                       }
 
 
-def parseOpts():
-       # Deferred imports
-       import getpass
-       import optparse
+                       try:
+                               self._downloader.process_info(info)
+                       except UnavailableVideoError, err:
+                               self._downloader.trouble(u'\nERROR: unable to download ' + mediaId)
+                               continue
 
 
-       def _format_option_string(option):
-               ''' ('-o', '--option') -> -o, --format METAVAR'''
 
 
-               opts = []
+class EscapistIE(InfoExtractor):
+       """Information extractor for The Escapist """
 
 
-               if option._short_opts: opts.append(option._short_opts[0])
-               if option._long_opts: opts.append(option._long_opts[0])
-               if len(opts) > 1: opts.insert(1, ', ')
+       _VALID_URL = r'^(https?://)?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<episode>[^/?]+)[/?]?.*$'
+       IE_NAME = u'escapist'
 
 
-               if option.takes_value(): opts.append(' %s' % option.metavar)
+       def report_extraction(self, showName):
+               self._downloader.to_screen(u'[escapist] %s: Extracting information' % showName)
 
 
-               return "".join(opts)
+       def report_config_download(self, showName):
+               self._downloader.to_screen(u'[escapist] %s: Downloading configuration' % showName)
 
 
-       def _find_term_columns():
-               columns = os.environ.get('COLUMNS', None)
-               if columns:
-                       return int(columns)
+       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:
                try:
-                       sp = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-                       out,err = sp.communicate()
-                       return out.split()[1]
-               except:
-                       pass
-               return None
+                       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
 
 
-       max_width = 80
-       max_help_position = 80
+               descMatch = re.search('<meta name="description" content="([^"]*)"', webPage)
+               description = htmlParser.unescape(descMatch.group(1))
+               imgMatch = re.search('<meta property="og:image" content="([^"]*)"', webPage)
+               imgUrl = htmlParser.unescape(imgMatch.group(1))
+               playerUrlMatch = re.search('<meta property="og:video" content="([^"]*)"', webPage)
+               playerUrl = htmlParser.unescape(playerUrlMatch.group(1))
+               configUrlMatch = re.search('config=(.*)$', playerUrl)
+               configUrl = urllib2.unquote(configUrlMatch.group(1))
 
 
-       # No need to wrap help messages if we're on a wide console
-       columns = _find_term_columns()
-       if columns: max_width = columns
+               self.report_config_download(showName)
+               try:
+                       configJSON = urllib2.urlopen(configUrl).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: unable to download configuration: ' + unicode(err))
+                       return
 
 
-       fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
-       fmt.format_option_strings = _format_option_string
+               # Technically, it's JavaScript, not JSON
+               configJSON = configJSON.replace("'", '"')
 
 
-       kw = {
-               'version'   : __version__,
-               'formatter' : fmt,
-               'usage' : '%prog [options] url...',
-               'conflict_handler' : 'resolve',
-       }
+               try:
+                       config = json.loads(configJSON)
+               except (ValueError,), err:
+                       self._downloader.trouble(u'ERROR: Invalid JSON in configuration file: ' + unicode(err))
+                       return
 
 
-       parser = optparse.OptionParser(**kw)
+               playlist = config['playlist']
+               videoUrl = playlist[1]['url']
 
 
-       # option groups
-       general        = optparse.OptionGroup(parser, 'General Options')
-       authentication = optparse.OptionGroup(parser, 'Authentication Options')
-       video_format   = optparse.OptionGroup(parser, 'Video Format Options')
-       postproc       = optparse.OptionGroup(parser, 'Post-processing Options')
-       filesystem     = optparse.OptionGroup(parser, 'Filesystem Options')
-       verbosity      = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
+               self._downloader.increment_downloads()
+               info = {
+                       'id': videoId,
+                       'url': videoUrl,
+                       'uploader': showName,
+                       'upload_date': None,
+                       'title': showName,
+                       'stitle': _simplify_title(showName),
+                       'ext': 'flv',
+                       'format': 'flv',
+                       'thumbnail': imgUrl,
+                       'description': description,
+                       'player_url': playerUrl,
+               }
 
 
-       general.add_option('-h', '--help',
-                       action='help', help='print this help text and exit')
-       general.add_option('-v', '--version',
+               try:
+                       self._downloader.process_info(info)
+               except UnavailableVideoError, err:
+                       self._downloader.trouble(u'\nERROR: unable to download ' + videoId)
+
+
+class CollegeHumorIE(InfoExtractor):
+       """Information extractor for collegehumor.com"""
+
+       _VALID_URL = r'^(?:https?://)?(?:www\.)?collegehumor\.com/video/(?P<videoid>[0-9]+)/(?P<shorttitle>.*)$'
+       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<internalvideoid>[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'<title>(.*?)\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"><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)
+               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'<meta name="description" content="(.*)"(?:\s*/)?>', 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<path>/?|(/MainFolder/(?:HomePage|CoursePage|VideoPage)\.php([?]course=(?P<course>[^&]+)(&video=(?P<video>[^&]+))?(&.*)?)?))$'
+       IE_NAME = u'stanfordoc'
+
+       def report_download_webpage(self, objid):
+               """Report information extraction."""
+               self._downloader.to_screen(u'[%s] %s: Downloading webpage' % (self.IE_NAME, objid))
+
+       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
+
+               if mobj.group('course') and mobj.group('video'): # A specific video
+                       course = mobj.group('course')
+                       video = mobj.group('video')
+                       info = {
+                               'id': _simplify_title(course + '_' + video),
+                       }
+
+                       self.report_extraction(info['id'])
+                       baseUrl = 'http://openclassroom.stanford.edu/MainFolder/courses/' + course + '/videos/'
+                       xmlUrl = baseUrl + video + '.xml'
+                       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' % unicode(err))
+                               return
+                       mdoc = xml.etree.ElementTree.fromstring(metaXml)
+                       try:
+                               info['title'] = mdoc.findall('./title')[0].text
+                               info['url'] = baseUrl + mdoc.findall('./videoFile')[0].text
+                       except IndexError:
+                               self._downloader.trouble(u'\nERROR: Invalid metadata XML file')
+                               return
+                       info['stitle'] = _simplify_title(info['title'])
+                       info['ext'] = info['url'].rpartition('.')[2]
+                       info['format'] = info['ext']
+                       self._downloader.increment_downloads()
+                       try:
+                               self._downloader.process_info(info)
+                       except UnavailableVideoError, err:
+                               self._downloader.trouble(u'\nERROR: unable to download video')
+               elif mobj.group('course'): # A course page
+                       unescapeHTML = HTMLParser.HTMLParser().unescape
+
+                       course = mobj.group('course')
+                       info = {
+                               'id': _simplify_title(course),
+                               'type': 'playlist',
+                       }
+
+                       self.report_download_webpage(info['id'])
+                       try:
+                               coursepage = urllib2.urlopen(url).read()
+                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                               self._downloader.trouble(u'ERROR: unable to download course info page: ' + unicode(err))
+                               return
+
+                       m = re.search('<h1>([^<]+)</h1>', coursepage)
+                       if m:
+                               info['title'] = unescapeHTML(m.group(1))
+                       else:
+                               info['title'] = info['id']
+                       info['stitle'] = _simplify_title(info['title'])
+
+                       m = re.search('<description>([^<]+)</description>', coursepage)
+                       if m:
+                               info['description'] = unescapeHTML(m.group(1))
+
+                       links = _orderedSet(re.findall('<a href="(VideoPage.php\?[^"]+)">', coursepage))
+                       info['list'] = [
+                               {
+                                       'type': 'reference',
+                                       'url': 'http://openclassroom.stanford.edu/MainFolder/' + unescapeHTML(vpage),
+                               }
+                                       for vpage in links]
+
+                       for entry in info['list']:
+                               assert entry['type'] == 'reference'
+                               self.extract(entry['url'])
+               else: # Root page
+                       unescapeHTML = HTMLParser.HTMLParser().unescape
+
+                       info = {
+                               'id': 'Stanford OpenClassroom',
+                               'type': 'playlist',
+                       }
+
+                       self.report_download_webpage(info['id'])
+                       rootURL = 'http://openclassroom.stanford.edu/MainFolder/HomePage.php'
+                       try:
+                               rootpage = urllib2.urlopen(rootURL).read()
+                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                               self._downloader.trouble(u'ERROR: unable to download course info page: ' + unicode(err))
+                               return
+
+                       info['title'] = info['id']
+                       info['stitle'] = _simplify_title(info['title'])
+
+                       links = _orderedSet(re.findall('<a href="(CoursePage.php\?[^"]+)">', rootpage))
+                       info['list'] = [
+                               {
+                                       'type': 'reference',
+                                       'url': 'http://openclassroom.stanford.edu/MainFolder/' + unescapeHTML(cpage),
+                               }
+                                       for cpage in links]
+
+                       for entry in info['list']:
+                               assert entry['type'] == 'reference'
+                               self.extract(entry['url'])
+
+class MTVIE(InfoExtractor):
+       """Information extractor for MTV.com"""
+
+       _VALID_URL = r'^(?P<proto>https?://)?(?:www\.)?mtv\.com/videos/[^/]+/(?P<videoid>[0-9]+)/[^/]+$'
+       IE_NAME = u'mtv'
+
+       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
+               if not mobj.group('proto'):
+                       url = 'http://' + url
+               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
+
+               mobj = re.search(r'<meta name="mtv_vt" content="([^"]+)"/>', webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract song name')
+                       return
+               song_name = _unescapeHTML(mobj.group(1).decode('iso-8859-1'))
+               mobj = re.search(r'<meta name="mtv_an" content="([^"]+)"/>', webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract performer')
+                       return
+               performer = _unescapeHTML(mobj.group(1).decode('iso-8859-1'))
+               video_title = performer + ' - ' + song_name 
+
+               mobj = re.search(r'<meta name="mtvn_uri" content="([^"]+)"/>', webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to mtvn_uri')
+                       return
+               mtvn_uri = mobj.group(1)
+
+               mobj = re.search(r'MTVN.Player.defaultPlaylistId = ([0-9]+);', webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract content id')
+                       return
+               content_id = mobj.group(1)
+
+               videogen_url = 'http://www.mtv.com/player/includes/mediaGen.jhtml?uri=' + mtvn_uri + '&id=' + content_id + '&vid=' + video_id + '&ref=www.mtvn.com&viewUri=' + mtvn_uri
+               self.report_extraction(video_id)
+               request = urllib2.Request(videogen_url)
+               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))
+                       return
+
+               mdoc = xml.etree.ElementTree.fromstring(metadataXml)
+               renditions = mdoc.findall('.//rendition')
+
+               # For now, always pick the highest quality.
+               rendition = renditions[-1]
+
+               try:
+                       _,_,ext = rendition.attrib['type'].partition('/')
+                       format = ext + '-' + rendition.attrib['width'] + 'x' + rendition.attrib['height'] + '_' + rendition.attrib['bitrate']
+                       video_url = rendition.find('./src').text
+               except KeyError:
+                       self._downloader.trouble('Invalid rendition field.')
+                       return
+
+               self._downloader.increment_downloads()
+               info = {
+                       'id': video_id,
+                       'url': video_url,
+                       'uploader': performer,
+                       'title': video_title,
+                       'stitle': _simplify_title(video_title),
+                       'ext': ext,
+                       'format': format,
+               }
+
+               try:
+                       self._downloader.process_info(info)
+               except UnavailableVideoError, err:
+                       self._downloader.trouble(u'\nERROR: unable to download ' + video_id)
+
+
+class PostProcessor(object):
+       """Post Processor class.
+
+       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
+       PostProcessor.
+
+       The chain will be stopped if one of them ever returns None or the end
+       of the chain is reached.
+
+       PostProcessor objects follow a "mutual registration" process similar
+       to InfoExtractor objects.
+       """
+
+       _downloader = None
+
+       def __init__(self, downloader=None):
+               self._downloader = downloader
+
+       def set_downloader(self, downloader):
+               """Sets the downloader for this PP."""
+               self._downloader = downloader
+
+       def run(self, information):
+               """Run the PostProcessor.
+
+               The "information" argument is a dictionary like the ones
+               composed by InfoExtractors. The only difference is that this
+               one has an extra field called "filepath" that points to the
+               downloaded file.
+
+               When this method returns None, the postprocessing chain is
+               stopped. However, this method may return an information
+               dictionary that will be passed to the next postprocessing
+               object in the chain. It can be the one it received after
+               changing some fields.
+
+               In addition, this method may raise a PostProcessingError
+               exception that will be taken into account by the downloader
+               it was called from.
+               """
+               return information # by default, do nothing
+
+class AudioConversionError(BaseException):
+       def __init__(self, message):
+               self.message = message
+
+class FFmpegExtractAudioPP(PostProcessor):
+
+       def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, keepvideo=False):
+               PostProcessor.__init__(self, downloader)
+               if preferredcodec is None:
+                       preferredcodec = 'best'
+               self._preferredcodec = preferredcodec
+               self._preferredquality = preferredquality
+               self._keepvideo = keepvideo
+
+       @staticmethod
+       def get_audio_codec(path):
+               try:
+                       cmd = ['ffprobe', '-show_streams', '--', _encodeFilename(path)]
+                       handle = subprocess.Popen(cmd, stderr=file(os.path.devnull, 'w'), stdout=subprocess.PIPE)
+                       output = handle.communicate()[0]
+                       if handle.wait() != 0:
+                               return None
+               except (IOError, OSError):
+                       return None
+               audio_codec = None
+               for line in output.split('\n'):
+                       if line.startswith('codec_name='):
+                               audio_codec = line.split('=')[1].strip()
+                       elif line.strip() == 'codec_type=audio' and audio_codec is not None:
+                               return audio_codec
+               return None
+
+       @staticmethod
+       def run_ffmpeg(path, out_path, codec, more_opts):
+               if codec is None:
+                       acodec_opts = []
+               else:
+                       acodec_opts = ['-acodec', codec]
+               cmd = ['ffmpeg', '-y', '-i', _encodeFilename(path), '-vn'] + acodec_opts + more_opts + ['--', _encodeFilename(out_path)]
+               try:
+                       p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+                       stdout,stderr = p.communicate()
+               except (IOError, OSError):
+                       e = sys.exc_info()[1]
+                       if isinstance(e, OSError) and e.errno == 2:
+                               raise AudioConversionError('ffmpeg not found. Please install ffmpeg.')
+                       else:
+                               raise e
+               if p.returncode != 0:
+                       msg = stderr.strip().split('\n')[-1]
+                       raise AudioConversionError(msg)
+
+       def run(self, information):
+               path = information['filepath']
+
+               filecodec = self.get_audio_codec(path)
+               if filecodec is None:
+                       self._downloader.to_stderr(u'WARNING: unable to obtain file audio codec with ffprobe')
+                       return None
+
+               more_opts = []
+               if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'):
+                       if self._preferredcodec == 'm4a' and filecodec == 'aac':
+                               # Lossless, but in another container
+                               acodec = 'copy'
+                               extension = self._preferredcodec
+                               more_opts = ['-absf', 'aac_adtstoasc']
+                       elif filecodec in ['aac', 'mp3', 'vorbis']:
+                               # Lossless if possible
+                               acodec = 'copy'
+                               extension = filecodec
+                               if filecodec == 'aac':
+                                       more_opts = ['-f', 'adts']
+                               if filecodec == 'vorbis':
+                                       extension = 'ogg'
+                       else:
+                               # MP3 otherwise.
+                               acodec = 'libmp3lame'
+                               extension = 'mp3'
+                               more_opts = []
+                               if self._preferredquality is not None:
+                                       more_opts += ['-ab', self._preferredquality]
+               else:
+                       # We convert the audio (lossy)
+                       acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis', 'wav': None}[self._preferredcodec]
+                       extension = self._preferredcodec
+                       more_opts = []
+                       if self._preferredquality is not None:
+                               more_opts += ['-ab', self._preferredquality]
+                       if self._preferredcodec == 'aac':
+                               more_opts += ['-f', 'adts']
+                       if self._preferredcodec == 'm4a':
+                               more_opts += ['-absf', 'aac_adtstoasc']
+                       if self._preferredcodec == 'vorbis':
+                               extension = 'ogg'
+                       if self._preferredcodec == 'wav':
+                               extension = 'wav'
+                               more_opts += ['-f', 'wav']
+
+               prefix, sep, ext = path.rpartition(u'.') # not os.path.splitext, since the latter does not work on unicode in all setups
+               new_path = prefix + sep + extension
+               self._downloader.to_screen(u'[ffmpeg] Destination: ' + new_path)
+               try:
+                       self.run_ffmpeg(path, new_path, acodec, more_opts)
+               except:
+                       etype,e,tb = sys.exc_info()
+                       if isinstance(e, AudioConversionError):
+                               self._downloader.to_stderr(u'ERROR: audio conversion failed: ' + e.message)
+                       else:
+                               self._downloader.to_stderr(u'ERROR: error running ffmpeg')
+                       return None
+
+               # Try to update the date time for extracted audio file.
+               if information.get('filetime') is not None:
+                       try:
+                               os.utime(_encodeFilename(new_path), (time.time(), information['filetime']))
+                       except:
+                               self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file')
+
+               if not self._keepvideo:
+                       try:
+                               os.remove(_encodeFilename(path))
+                       except (IOError, OSError):
+                               self._downloader.to_stderr(u'WARNING: Unable to remove downloaded video file')
+                               return None
+
+               information['filepath'] = new_path
+               return information
+
+
+def updateSelf(downloader, filename):
+       ''' Update the program file with the latest version from the repository '''
+       # Note: downloader only used for options
+       if not os.access(filename, os.W_OK):
+               sys.exit('ERROR: no write permissions on %s' % filename)
+
+       downloader.to_screen(u'Updating to latest version...')
+
+       try:
+               try:
+                       urlh = urllib.urlopen(UPDATE_URL)
+                       newcontent = urlh.read()
+                       
+                       vmatch = re.search("__version__ = '([^']+)'", newcontent)
+                       if vmatch is not None and vmatch.group(1) == __version__:
+                               downloader.to_screen(u'youtube-dl is up-to-date (' + __version__ + ')')
+                               return
+               finally:
+                       urlh.close()
+       except (IOError, OSError), err:
+               sys.exit('ERROR: unable to download latest version')
+
+       try:
+               outf = open(filename, 'wb')
+               try:
+                       outf.write(newcontent)
+               finally:
+                       outf.close()
+       except (IOError, OSError), err:
+               sys.exit('ERROR: unable to overwrite current version')
+
+       downloader.to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
+
+def parseOpts():
+       def _readOptions(filename_bytes):
+               try:
+                       optionf = open(filename_bytes)
+               except IOError:
+                       return [] # silently skip if file is not present
+               try:
+                       res = []
+                       for l in optionf:
+                               res += shlex.split(l, comments=True)
+               finally:
+                       optionf.close()
+               return res
+
+       def _format_option_string(option):
+               ''' ('-o', '--option') -> -o, --format METAVAR'''
+
+               opts = []
+
+               if option._short_opts: opts.append(option._short_opts[0])
+               if option._long_opts: opts.append(option._long_opts[0])
+               if len(opts) > 1: opts.insert(1, ', ')
+
+               if option.takes_value(): opts.append(' %s' % option.metavar)
+
+               return "".join(opts)
+
+       def _find_term_columns():
+               columns = os.environ.get('COLUMNS', None)
+               if columns:
+                       return int(columns)
+
+               try:
+                       sp = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+                       out,err = sp.communicate()
+                       return int(out.split()[1])
+               except:
+                       pass
+               return None
+
+       max_width = 80
+       max_help_position = 80
+
+       # No need to wrap help messages if we're on a wide console
+       columns = _find_term_columns()
+       if columns: max_width = columns
+
+       fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
+       fmt.format_option_strings = _format_option_string
+
+       kw = {
+               'version'   : __version__,
+               'formatter' : fmt,
+               'usage' : '%prog [options] url [url...]',
+               'conflict_handler' : 'resolve',
+       }
+
+       parser = optparse.OptionParser(**kw)
+
+       # option groups
+       general        = optparse.OptionGroup(parser, 'General Options')
+       selection      = optparse.OptionGroup(parser, 'Video Selection')
+       authentication = optparse.OptionGroup(parser, 'Authentication Options')
+       video_format   = optparse.OptionGroup(parser, 'Video Format Options')
+       postproc       = optparse.OptionGroup(parser, 'Post-processing Options')
+       filesystem     = optparse.OptionGroup(parser, 'Filesystem Options')
+       verbosity      = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
+
+       general.add_option('-h', '--help',
+                       action='help', help='print this help text and exit')
+       general.add_option('-v', '--version',
                        action='version', help='print program version and exit')
        general.add_option('-U', '--update',
                        action='version', help='print program version and exit')
        general.add_option('-U', '--update',
-                       action='store_true', dest='update_self', help='update this program to latest stable version')
+                       action='store_true', dest='update_self', help='update this program to latest version')
        general.add_option('-i', '--ignore-errors',
                        action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
        general.add_option('-r', '--rate-limit',
                        dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
        general.add_option('-R', '--retries',
                        dest='retries', metavar='RETRIES', help='number of retries (default is 10)', default=10)
        general.add_option('-i', '--ignore-errors',
                        action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
        general.add_option('-r', '--rate-limit',
                        dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
        general.add_option('-R', '--retries',
                        dest='retries', metavar='RETRIES', help='number of retries (default is 10)', default=10)
-       general.add_option('--playlist-start',
-                       dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
-       general.add_option('--playlist-end',
-                       dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
        general.add_option('--dump-user-agent',
                        action='store_true', dest='dump_user_agent',
                        help='display the current browser identification', default=False)
        general.add_option('--dump-user-agent',
                        action='store_true', dest='dump_user_agent',
                        help='display the current browser identification', default=False)
+       general.add_option('--list-extractors',
+                       action='store_true', dest='list_extractors',
+                       help='List all supported extractors and the URLs they would handle', default=False)
+
+       selection.add_option('--playlist-start',
+                       dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
+       selection.add_option('--playlist-end',
+                       dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
+       selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
+       selection.add_option('--reject-title', dest='rejecttitle', metavar='REGEX',help='skip download for matching titles (regex or caseless sub-string)')
+       selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
 
        authentication.add_option('-u', '--username',
                        dest='username', metavar='USERNAME', help='account username')
 
        authentication.add_option('-u', '--username',
                        dest='username', metavar='USERNAME', help='account username')
@@ -3062,15 +4386,27 @@ def parseOpts():
        video_format.add_option('-f', '--format',
                        action='store', dest='format', metavar='FORMAT', help='video format code')
        video_format.add_option('--all-formats',
        video_format.add_option('-f', '--format',
                        action='store', dest='format', metavar='FORMAT', help='video format code')
        video_format.add_option('--all-formats',
-                       action='store_const', dest='format', help='download all available video formats', const='-1')
+                       action='store_const', dest='format', help='download all available video formats', const='all')
+       video_format.add_option('--prefer-free-formats',
+                       action='store_true', dest='prefer_free_formats', default=False, help='prefer free video formats unless a specific one is requested')
        video_format.add_option('--max-quality',
                        action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
        video_format.add_option('--max-quality',
                        action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
+       video_format.add_option('-F', '--list-formats',
+                       action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
+       video_format.add_option('--write-srt',
+                       action='store_true', dest='writesubtitles',
+                       help='write video closed captions to a .srt file (currently youtube only)', default=False)
+       video_format.add_option('--srt-lang',
+                       action='store', dest='subtitleslang', metavar='LANG',
+                       help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
 
 
        verbosity.add_option('-q', '--quiet',
                        action='store_true', dest='quiet', help='activates quiet mode', default=False)
        verbosity.add_option('-s', '--simulate',
 
 
        verbosity.add_option('-q', '--quiet',
                        action='store_true', dest='quiet', help='activates quiet mode', default=False)
        verbosity.add_option('-s', '--simulate',
-                       action='store_true', dest='simulate', help='do not download video', default=False)
+                       action='store_true', dest='simulate', help='do not download the video and do not write anything to disk', default=False)
+       verbosity.add_option('--skip-download',
+                       action='store_true', dest='skip_download', help='do not download the video', default=False)
        verbosity.add_option('-g', '--get-url',
                        action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
        verbosity.add_option('-e', '--get-title',
        verbosity.add_option('-g', '--get-url',
                        action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
        verbosity.add_option('-e', '--get-title',
@@ -3084,11 +4420,16 @@ def parseOpts():
        verbosity.add_option('--get-filename',
                        action='store_true', dest='getfilename',
                        help='simulate, quiet but print output filename', default=False)
        verbosity.add_option('--get-filename',
                        action='store_true', dest='getfilename',
                        help='simulate, quiet but print output filename', default=False)
+       verbosity.add_option('--get-format',
+                       action='store_true', dest='getformat',
+                       help='simulate, quiet but print output format', default=False)
        verbosity.add_option('--no-progress',
                        action='store_true', dest='noprogress', help='do not print progress bar', default=False)
        verbosity.add_option('--console-title',
                        action='store_true', dest='consoletitle',
                        help='display progress in console titlebar', default=False)
        verbosity.add_option('--no-progress',
                        action='store_true', dest='noprogress', help='do not print progress bar', default=False)
        verbosity.add_option('--console-title',
                        action='store_true', dest='consoletitle',
                        help='display progress in console titlebar', default=False)
+       verbosity.add_option('-v', '--verbose',
+                       action='store_true', dest='verbose', help='print various debugging information', default=False)
 
 
        filesystem.add_option('-t', '--title',
 
 
        filesystem.add_option('-t', '--title',
@@ -3099,15 +4440,18 @@ def parseOpts():
                        action='store_true', dest='autonumber',
                        help='number downloaded files starting from 00000', default=False)
        filesystem.add_option('-o', '--output',
                        action='store_true', dest='autonumber',
                        help='number downloaded files starting from 00000', default=False)
        filesystem.add_option('-o', '--output',
-                       dest='outtmpl', metavar='TEMPLATE', help='output filename template')
+                       dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(stitle)s to get the title, %(uploader)s for the uploader name, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), and %% for a literal percent. Use - to output to stdout.')
        filesystem.add_option('-a', '--batch-file',
                        dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
        filesystem.add_option('-w', '--no-overwrites',
                        action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
        filesystem.add_option('-c', '--continue',
        filesystem.add_option('-a', '--batch-file',
                        dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
        filesystem.add_option('-w', '--no-overwrites',
                        action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
        filesystem.add_option('-c', '--continue',
-                       action='store_true', dest='continue_dl', help='resume partially downloaded files', default=False)
+                       action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
+       filesystem.add_option('--no-continue',
+                       action='store_false', dest='continue_dl',
+                       help='do not resume partially downloaded files (restart from beginning)')
        filesystem.add_option('--cookies',
        filesystem.add_option('--cookies',
-                       dest='cookiefile', metavar='FILE', help='file to dump cookie jar to')
+                       dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
        filesystem.add_option('--no-part',
                        action='store_true', dest='nopart', help='do not use .part files', default=False)
        filesystem.add_option('--no-mtime',
        filesystem.add_option('--no-part',
                        action='store_true', dest='nopart', help='do not use .part files', default=False)
        filesystem.add_option('--no-mtime',
@@ -3124,21 +4468,69 @@ def parseOpts():
        postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
                        help='convert video files to audio-only files (requires ffmpeg and ffprobe)')
        postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
        postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
                        help='convert video files to audio-only files (requires ffmpeg and ffprobe)')
        postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
-                       help='"best", "aac" or "mp3"; best by default')
+                       help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default')
+       postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
+                       help='ffmpeg audio bitrate specification, 128k by default')
+       postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
+                       help='keeps the video file on disk after the post-processing; the video is erased by default')
 
 
        parser.add_option_group(general)
 
 
        parser.add_option_group(general)
+       parser.add_option_group(selection)
        parser.add_option_group(filesystem)
        parser.add_option_group(verbosity)
        parser.add_option_group(video_format)
        parser.add_option_group(authentication)
        parser.add_option_group(postproc)
 
        parser.add_option_group(filesystem)
        parser.add_option_group(verbosity)
        parser.add_option_group(video_format)
        parser.add_option_group(authentication)
        parser.add_option_group(postproc)
 
-       opts, args = parser.parse_args()
+       xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
+       if xdg_config_home:
+               userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
+       else:
+               userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
+       argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
+       opts, args = parser.parse_args(argv)
 
        return parser, opts, args
 
 
        return parser, opts, args
 
-def main():
+def gen_extractors():
+       """ Return a list of an instance of every supported extractor.
+       The order does matter; the first extractor matched is the one handling the URL.
+       """
+       youtube_ie = YoutubeIE()
+       google_ie = GoogleIE()
+       yahoo_ie = YahooIE()
+       return [
+               YoutubePlaylistIE(youtube_ie),
+               YoutubeUserIE(youtube_ie),
+               YoutubeSearchIE(youtube_ie),
+               youtube_ie,
+               MetacafeIE(youtube_ie),
+               DailymotionIE(),
+               google_ie,
+               GoogleSearchIE(google_ie),
+               PhotobucketIE(),
+               yahoo_ie,
+               YahooSearchIE(yahoo_ie),
+               DepositFilesIE(),
+               FacebookIE(),
+               BlipTVIE(),
+               VimeoIE(),
+               MyVideoIE(),
+               ComedyCentralIE(),
+               EscapistIE(),
+               CollegeHumorIE(),
+               XVideosIE(),
+               SoundcloudIE(),
+               InfoQIE(),
+               MixcloudIE(),
+               StanfordOpenClassroomIE(),
+               MTVIE(),
+
+               GenericIE()
+       ]
+
+def _real_main():
        parser, opts, args = parseOpts()
 
        # Open appropriate CookieJar
        parser, opts, args = parseOpts()
 
        # Open appropriate CookieJar
@@ -3157,11 +4549,6 @@ def main():
                print std_headers['User-Agent']
                sys.exit(0)
 
                print std_headers['User-Agent']
                sys.exit(0)
 
-       # General configuration
-       cookie_processor = urllib2.HTTPCookieProcessor(jar)
-       urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler(), cookie_processor, YoutubeDLHandler()))
-       socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
-
        # Batch file verification
        batchurls = []
        if opts.batchfile is not None:
        # Batch file verification
        batchurls = []
        if opts.batchfile is not None:
@@ -3177,6 +4564,27 @@ def main():
                        sys.exit(u'ERROR: batch file could not be read')
        all_urls = batchurls + args
 
                        sys.exit(u'ERROR: batch file could not be read')
        all_urls = batchurls + args
 
+       # General configuration
+       cookie_processor = urllib2.HTTPCookieProcessor(jar)
+       proxy_handler = urllib2.ProxyHandler()
+       opener = urllib2.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
+       urllib2.install_opener(opener)
+       socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
+
+       if opts.verbose:
+               print(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
+
+       extractors = gen_extractors()
+
+       if opts.list_extractors:
+               for ie in extractors:
+                       print(ie.IE_NAME)
+                       matchedUrls = filter(lambda url: ie.suitable(url), all_urls)
+                       all_urls = filter(lambda url: url not in matchedUrls, all_urls)
+                       for mu in matchedUrls:
+                               print(u'  ' + mu)
+               sys.exit(0)
+
        # Conflicting, missing and erroneous options
        if opts.usenetrc and (opts.username is not None or opts.password is not None):
                parser.error(u'using .netrc conflicts with giving username/password')
        # Conflicting, missing and erroneous options
        if opts.usenetrc and (opts.username is not None or opts.password is not None):
                parser.error(u'using .netrc conflicts with giving username/password')
@@ -3211,40 +4619,26 @@ def main():
        except (TypeError, ValueError), err:
                parser.error(u'invalid playlist end number specified')
        if opts.extractaudio:
        except (TypeError, ValueError), err:
                parser.error(u'invalid playlist end number specified')
        if opts.extractaudio:
-               if opts.audioformat not in ['best', 'aac', 'mp3']:
+               if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a', 'wav']:
                        parser.error(u'invalid audio format specified')
 
                        parser.error(u'invalid audio format specified')
 
-       # Information extractors
-       youtube_ie = YoutubeIE()
-       metacafe_ie = MetacafeIE(youtube_ie)
-       dailymotion_ie = DailymotionIE()
-       youtube_pl_ie = YoutubePlaylistIE(youtube_ie)
-       youtube_user_ie = YoutubeUserIE(youtube_ie)
-       youtube_search_ie = YoutubeSearchIE(youtube_ie)
-       google_ie = GoogleIE()
-       google_search_ie = GoogleSearchIE(google_ie)
-       photobucket_ie = PhotobucketIE()
-       yahoo_ie = YahooIE()
-       yahoo_search_ie = YahooSearchIE(yahoo_ie)
-       deposit_files_ie = DepositFilesIE()
-       facebook_ie = FacebookIE()
-       bliptv_ie = BlipTVIE()
-       generic_ie = GenericIE()
-
        # File downloader
        fd = FileDownloader({
                'usenetrc': opts.usenetrc,
                'username': opts.username,
                'password': opts.password,
        # File downloader
        fd = FileDownloader({
                'usenetrc': opts.usenetrc,
                'username': opts.username,
                'password': opts.password,
-               'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename),
+               'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
                'forceurl': opts.geturl,
                'forcetitle': opts.gettitle,
                'forcethumbnail': opts.getthumbnail,
                'forcedescription': opts.getdescription,
                'forcefilename': opts.getfilename,
                'forceurl': opts.geturl,
                'forcetitle': opts.gettitle,
                'forcethumbnail': opts.getthumbnail,
                'forcedescription': opts.getdescription,
                'forcefilename': opts.getfilename,
-               'simulate': (opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename),
+               'forceformat': opts.getformat,
+               'simulate': opts.simulate,
+               'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
                'format': opts.format,
                'format_limit': opts.format_limit,
                'format': opts.format,
                'format_limit': opts.format_limit,
+               'listformats': opts.listformats,
                'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
                        or (opts.format == '-1' and opts.usetitle and u'%(stitle)s-%(id)s-%(format)s.%(ext)s')
                        or (opts.format == '-1' and opts.useliteral and u'%(title)s-%(id)s-%(format)s.%(ext)s')
                'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
                        or (opts.format == '-1' and opts.usetitle and u'%(stitle)s-%(id)s-%(format)s.%(ext)s')
                        or (opts.format == '-1' and opts.useliteral and u'%(title)s-%(id)s-%(format)s.%(ext)s')
@@ -3269,29 +4663,20 @@ def main():
                'updatetime': opts.updatetime,
                'writedescription': opts.writedescription,
                'writeinfojson': opts.writeinfojson,
                'updatetime': opts.updatetime,
                'writedescription': opts.writedescription,
                'writeinfojson': opts.writeinfojson,
+               'writesubtitles': opts.writesubtitles,
+               'subtitleslang': opts.subtitleslang,
+               'matchtitle': opts.matchtitle,
+               'rejecttitle': opts.rejecttitle,
+               'max_downloads': opts.max_downloads,
+               'prefer_free_formats': opts.prefer_free_formats,
+               'verbose': opts.verbose,
                })
                })
-       fd.add_info_extractor(youtube_search_ie)
-       fd.add_info_extractor(youtube_pl_ie)
-       fd.add_info_extractor(youtube_user_ie)
-       fd.add_info_extractor(metacafe_ie)
-       fd.add_info_extractor(dailymotion_ie)
-       fd.add_info_extractor(youtube_ie)
-       fd.add_info_extractor(google_ie)
-       fd.add_info_extractor(google_search_ie)
-       fd.add_info_extractor(photobucket_ie)
-       fd.add_info_extractor(yahoo_ie)
-       fd.add_info_extractor(yahoo_search_ie)
-       fd.add_info_extractor(deposit_files_ie)
-       fd.add_info_extractor(facebook_ie)
-       fd.add_info_extractor(bliptv_ie)
-
-       # This must come last since it's the
-       # fallback if none of the others work
-       fd.add_info_extractor(generic_ie)
+       for extractor in extractors:
+               fd.add_info_extractor(extractor)
 
        # PostProcessors
        if opts.extractaudio:
 
        # PostProcessors
        if opts.extractaudio:
-               fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat))
+               fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, keepvideo=opts.keepvideo))
 
        # Update version
        if opts.update_self:
 
        # Update version
        if opts.update_self:
@@ -3303,7 +4688,12 @@ def main():
                        parser.error(u'you must provide at least one URL')
                else:
                        sys.exit()
                        parser.error(u'you must provide at least one URL')
                else:
                        sys.exit()
-       retcode = fd.download(all_urls)
+
+       try:
+               retcode = fd.download(all_urls)
+       except MaxDownloadsReached:
+               fd.to_screen(u'--max-download limit reached, aborting.')
+               retcode = 101
 
        # Dump cookie jar if requested
        if opts.cookiefile is not None:
 
        # Dump cookie jar if requested
        if opts.cookiefile is not None:
@@ -3314,10 +4704,9 @@ def main():
 
        sys.exit(retcode)
 
 
        sys.exit(retcode)
 
-
-if __name__ == '__main__':
+def main():
        try:
        try:
-               main()
+               _real_main()
        except DownloadError:
                sys.exit(1)
        except SameFileError:
        except DownloadError:
                sys.exit(1)
        except SameFileError:
@@ -3325,4 +4714,7 @@ if __name__ == '__main__':
        except KeyboardInterrupt:
                sys.exit(u'\nERROR: Interrupted by user')
 
        except KeyboardInterrupt:
                sys.exit(u'\nERROR: Interrupted by user')
 
+if __name__ == '__main__':
+       main()
+
 # vim: set ts=4 sw=4 sts=4 noet ai si filetype=python:
 # vim: set ts=4 sw=4 sts=4 noet ai si filetype=python: