Merge PR #422 from 'kevinamadeus/master'
authorFilippo Valsorda <filippo.valsorda@gmail.com>
Tue, 9 Oct 2012 08:48:49 +0000 (10:48 +0200)
committerFilippo Valsorda <filippo.valsorda@gmail.com>
Tue, 9 Oct 2012 08:48:49 +0000 (10:48 +0200)
Add InfoExtractor for Google Plus video
(with fixes)

1  2 
youtube_dl/InfoExtractors.py
youtube_dl/__init__.py

index 9e5ea7c61870f19ae30cea7670867584226123f8,ddb9fbca1fc9f64739d4e4e2e09094911cc3457f..f97611cb9b4ab681eb21f760db4c71ea1b2da98a
@@@ -13,8 -13,6 +13,8 @@@ import urlli
  import urllib2
  import email.utils
  import xml.etree.ElementTree
 +import random
 +import math
  from urlparse import parse_qs
  
  try:
@@@ -97,25 -95,7 +97,25 @@@ class InfoExtractor(object)
  class YoutubeIE(InfoExtractor):
        """Information extractor for youtube.com."""
  
 -      _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$'
 +      _VALID_URL = r"""^
 +                       (
 +                           (?:https?://)?                                       # http(s):// (optional)
 +                           (?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|
 +                              tube\.majestyc\.net/)                             # the various hostnames, with wildcard subdomains
 +                           (?!view_play_list|my_playlists|artist|playlist)      # ignore playlist URLs
 +                           (?:                                                  # the various things that can precede the ID:
 +                               (?:(?:v|embed|e)/)                               # v/ or embed/ or e/
 +                               |(?:                                             # or the v= param in all its forms
 +                                   (?:watch(?:_popup)?(?:\.php)?)?              # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
 +                                   (?:\?|\#!?)                                  # the params delimiter ? or # or #!
 +                                   (?:.+&)?                                     # any other preceding param (like /?s=tuff&v=xxxx)
 +                                   v=
 +                               )
 +                           )?                                                   # optional -> youtube.com/xxxx is OK
 +                       )?                                                       # all until now is optional -> you can pass the naked ID
 +                       ([0-9A-Za-z_-]+)                                         # here is it! the YouTube video ID
 +                       (?(1).+)?                                                # if we found the ID, everything can follow
 +                       $"""
        _LANG_URL = r'http://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
        _LOGIN_URL = 'https://www.youtube.com/signup?next=/&gl=US&hl=en'
        _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
        }       
        IE_NAME = u'youtube'
  
 +      def suitable(self, url):
 +              """Receives a URL and returns True if suitable for this IE."""
 +              return re.match(self._VALID_URL, url, re.VERBOSE) is not None
 +
        def report_lang(self):
                """Report attempt to set language."""
                self._downloader.to_screen(u'[youtube] Setting language')
                        url = 'http://www.youtube.com/' + urllib.unquote(mobj.group(1)).lstrip('/')
  
                # Extract video id from URL
 -              mobj = re.match(self._VALID_URL, url)
 +              mobj = re.match(self._VALID_URL, url, re.VERBOSE)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
                        url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',')
                        url_data = [parse_qs(uds) for uds in url_data_strs]
                        url_data = filter(lambda ud: 'itag' in ud and 'url' in ud, url_data)
 -                      url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
 +                      url_map = dict((ud['itag'][0], ud['url'][0] + '&signature=' + ud['sig'][0]) for ud in url_data)
  
                        format_limit = self._downloader.params.get('format_limit', None)
                        available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats
@@@ -616,7 -592,7 +616,7 @@@ class MetacafeIE(InfoExtractor)
  class DailymotionIE(InfoExtractor):
        """Information Extractor for Dailymotion"""
  
 -      _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)'
 +      _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)'
        IE_NAME = u'dailymotion'
  
        def __init__(self, downloader=None):
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
  
 -              video_id = mobj.group(1)
 +              video_id = mobj.group(1).split('_')[0].split('?')[0]
  
 -              video_extension = 'flv'
 +              video_extension = 'mp4'
  
                # Retrieve video webpage to extract further information
                request = urllib2.Request(url)
  
                # Extract URL, uploader and title from webpage
                self.report_extraction(video_id)
 -              mobj = re.search(r'(?i)addVariable\(\"sequence\"\s*,\s*\"([^\"]+?)\"\)', webpage)
 +              mobj = re.search(r'\s*var flashvars = (.*)', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract media URL')
                        return
 -              sequence = urllib.unquote(mobj.group(1))
 -              mobj = re.search(r',\"sdURL\"\:\"([^\"]+?)\",', sequence)
 +              flashvars = urllib.unquote(mobj.group(1))
 +              if 'hqURL' in flashvars: max_quality = 'hqURL'
 +              elif 'sdURL' in flashvars: max_quality = 'sdURL'
 +              else: max_quality = 'ldURL'
 +              mobj = re.search(r'"' + max_quality + r'":"(.+?)"', flashvars)
 +              if mobj is None:
 +                      mobj = re.search(r'"video_url":"(.*?)",', flashvars)
                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 = urllib.unquote(mobj.group(1)).replace('\\/', '/')
  
 -              video_url = mediaURL
 +              # TODO: support choosing qualities
  
                mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)
                if mobj is None:
                        return
                video_uploader = mobj.group(1)
  
 +              video_upload_date = u'NA'
 +              mobj = re.search(r'<div class="[^"]*uploaded_cont[^"]*" title="[^"]*">([0-9]{2})-([0-9]{2})-([0-9]{4})</div>', webpage)
 +              if mobj is not None:
 +                      video_upload_date = mobj.group(3) + mobj.group(2) + mobj.group(1)
 +
                return [{
                        'id':           video_id.decode('utf-8'),
                        'url':          video_url.decode('utf-8'),
                        'uploader':     video_uploader.decode('utf-8'),
 -                      'upload_date':  u'NA',
 +                      'upload_date':  video_upload_date,
                        'title':        video_title,
                        'ext':          video_extension.decode('utf-8'),
                        'format':       u'NA',
@@@ -1503,7 -1471,7 +1503,7 @@@ class YoutubePlaylistIE(InfoExtractor)
  
        _VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
        _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
 -      _VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=(PL)?%s&'
 +      _VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=.*?%s'
        _MORE_PAGES_INDICATOR = r'yt-uix-pager-next'
        IE_NAME = u'youtube:playlist'
  
@@@ -2988,189 -2956,126 +2988,313 @@@ class MTVIE(InfoExtractor)
  
                return [info]
  
 -      _VALID_URL = r'(?:https://)?plus\.google\.com/(\d+)/posts/(\w+)'
 +
 +class YoukuIE(InfoExtractor):
 +
 +      _VALID_URL =  r'(?:http://)?v\.youku\.com/v_show/id_(?P<ID>[A-Za-z0-9]+)\.html'
 +      IE_NAME = u'Youku'
 +
 +      def __init__(self, downloader=None):
 +              InfoExtractor.__init__(self, downloader)
 +
 +      def report_download_webpage(self, file_id):
 +              """Report webpage download."""
 +              self._downloader.to_screen(u'[Youku] %s: Downloading webpage' % file_id)
 +
 +      def report_extraction(self, file_id):
 +              """Report information extraction."""
 +              self._downloader.to_screen(u'[Youku] %s: Extracting information' % file_id)
 +
 +      def _gen_sid(self):
 +              nowTime = int(time.time() * 1000)
 +              random1 = random.randint(1000,1998)
 +              random2 = random.randint(1000,9999)
 +
 +              return "%d%d%d" %(nowTime,random1,random2)
 +
 +      def _get_file_ID_mix_string(self, seed):
 +              mixed = []
 +              source = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890")
 +              seed = float(seed)
 +              for i in range(len(source)):
 +                      seed  =  (seed * 211 + 30031 ) % 65536
 +                      index  =  math.floor(seed / 65536 * len(source) )
 +                      mixed.append(source[int(index)])
 +                      source.remove(source[int(index)])
 +              #return ''.join(mixed)
 +              return mixed
 +
 +      def _get_file_id(self, fileId, seed):
 +              mixed = self._get_file_ID_mix_string(seed)
 +              ids = fileId.split('*')
 +              realId = []
 +              for ch in ids:
 +                      if ch:
 +                              realId.append(mixed[int(ch)])
 +              return ''.join(realId)
 +
 +      def _real_extract(self, url):
 +              mobj = re.match(self._VALID_URL, url)
 +              if mobj is None:
 +                      self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
 +                      return
 +              video_id = mobj.group('ID')
 +
 +              info_url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + video_id
 +
 +              request = urllib2.Request(info_url, None, std_headers)
 +              try:
 +                      self.report_download_webpage(video_id)
 +                      jsondata = urllib2.urlopen(request).read()
 +              except (urllib2.URLError, httplib.HTTPException, socket.error) as err:
 +                      self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
 +                      return
 +
 +              self.report_extraction(video_id)
 +              try:
 +                      config = json.loads(jsondata)
 +
 +                      video_title =  config['data'][0]['title']
 +                      seed = config['data'][0]['seed']
 +
 +                      format = self._downloader.params.get('format', None)
 +                      supported_format = config['data'][0]['streamfileids'].keys()
 +
 +                      if format is None or format == 'best':
 +                              if 'hd2' in supported_format:
 +                                      format = 'hd2'
 +                              else:
 +                                      format = 'flv'
 +                              ext = u'flv'
 +                      elif format == 'worst':
 +                              format = 'mp4'
 +                              ext = u'mp4'
 +                      else:
 +                              format = 'flv'
 +                              ext = u'flv'
 +
 +
 +                      fileid = config['data'][0]['streamfileids'][format]
 +                      seg_number = len(config['data'][0]['segs'][format])
 +
 +                      keys=[]
 +                      for i in xrange(seg_number):
 +                              keys.append(config['data'][0]['segs'][format][i]['k'])
 +
 +                      #TODO check error
 +                      #youku only could be viewed from mainland china
 +              except:
 +                      self._downloader.trouble(u'ERROR: unable to extract info section')
 +                      return
 +
 +              files_info=[]
 +              sid = self._gen_sid()
 +              fileid = self._get_file_id(fileid, seed)
 +
 +              #column 8,9 of fileid represent the segment number
 +              #fileid[7:9] should be changed
 +              for index, key in enumerate(keys):
 +
 +                      temp_fileid = '%s%02X%s' % (fileid[0:8], index, fileid[10:])
 +                      download_url = 'http://f.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key)
 +
 +                      info = {
 +                              'id': '%s_part%02d' % (video_id, index),
 +                              'url': download_url,
 +                              'uploader': None,
 +                              'title': video_title,
 +                              'ext': ext,
 +                              'format': u'NA'
 +                      }
 +                      files_info.append(info)
 +
 +              return files_info
 +
 +
 +class XNXXIE(InfoExtractor):
 +      """Information extractor for xnxx.com"""
 +
 +      _VALID_URL = r'^http://video\.xnxx\.com/video([0-9]+)/(.*)'
 +      IE_NAME = u'xnxx'
 +      VIDEO_URL_RE = r'flv_url=(.*?)&amp;'
 +      VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM'
 +      VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&amp;'
 +
 +      def report_webpage(self, video_id):
 +              """Report information extraction"""
 +              self._downloader.to_screen(u'[%s] %s: Downloading webpage' % (self.IE_NAME, video_id))
 +
 +      def report_extraction(self, video_id):
 +              """Report information extraction"""
 +              self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
 +
 +      def _real_extract(self, url):
 +              mobj = re.match(self._VALID_URL, url)
 +              if mobj is None:
 +                      self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
 +                      return
 +              video_id = mobj.group(1).decode('utf-8')
 +
 +              self.report_webpage(video_id)
 +
 +              # Get webpage content
 +              try:
 +                      webpage = urllib2.urlopen(url).read()
 +              except (urllib2.URLError, httplib.HTTPException, socket.error), err:
 +                      self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % err)
 +                      return
 +
 +              result = re.search(self.VIDEO_URL_RE, webpage)
 +              if result is None:
 +                      self._downloader.trouble(u'ERROR: unable to extract video url')
 +                      return
 +              video_url = urllib.unquote(result.group(1).decode('utf-8'))
 +
 +              result = re.search(self.VIDEO_TITLE_RE, webpage)
 +              if result is None:
 +                      self._downloader.trouble(u'ERROR: unable to extract video title')
 +                      return
 +              video_title = result.group(1).decode('utf-8')
 +
 +              result = re.search(self.VIDEO_THUMB_RE, webpage)
 +              if result is None:
 +                      self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
 +                      return
 +              video_thumbnail = result.group(1).decode('utf-8')
 +
 +              info = {'id': video_id,
 +                              'url': video_url,
 +                              'uploader': None,
 +                              'upload_date': None,
 +                              'title': video_title,
 +                              'ext': 'flv',
 +                              'format': 'flv',
 +                              'thumbnail': video_thumbnail,
 +                              'description': None,
 +                              'player_url': None}
 +
 +              return [info]
++
++
+ class GooglePlusIE(InfoExtractor):
+       """Information extractor for plus.google.com."""
 -                      self.report_extract_entry(post_url)
++      _VALID_URL = r'(?:https://)?plus\.google\.com/(?:\w+/)*?(\d+)/posts/(\w+)'
+       IE_NAME = u'plus.google'
+       def __init__(self, downloader=None):
+               InfoExtractor.__init__(self, downloader)
+       def report_extract_entry(self, url):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Downloading entry: %s' % url.decode('utf-8'))
+       def report_date(self, upload_date):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Entry date: %s' % upload_date)
+       def report_uploader(self, uploader):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Uploader: %s' % uploader.decode('utf-8'))
+       def report_title(self, video_title):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Title: %s' % video_title.decode('utf-8'))
+       def report_extract_vid_page(self, video_page):
+               """Report information extraction."""
+               self._downloader.to_screen(u'[plus.google] Extracting video page: %s' % video_page.decode('utf-8'))
+       def _real_extract(self, url):
+               # Extract id from URL
+               mobj = re.match(self._VALID_URL, url)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
+                       return
+               post_url = mobj.group(0)
+               video_id = mobj.group(2)
+               video_extension = 'flv'
+               # Step 1, Retrieve post webpage to extract further information
++              self.report_extract_entry(post_url)
+               request = urllib2.Request(post_url)
+               try:
 -                      """Convert timestring to a format suitable for filename"""
+                       webpage = urllib2.urlopen(request).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: Unable to retrieve entry webpage: %s' % str(err))
+                       return
+               # Extract update date
+               upload_date = u'NA'
+               pattern = 'title="Timestamp">(.*?)</a>'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       upload_date = mobj.group(1)
 -              """Get the first line for title"""
++                      # Convert timestring to a format suitable for filename
+                       upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d")
+                       upload_date = upload_date.strftime('%Y%m%d')
+               self.report_date(upload_date)
+               # Extract uploader
+               uploader = u'NA'
+               pattern = r'rel\="author".*?>(.*?)</a>'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       uploader = mobj.group(1)
+               self.report_uploader(uploader)
+               # Extract title
 -              pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\s<"]'
++              # Get the first line for title
+               video_title = u'NA'
 -              if mobj is None:
++              pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       video_title = mobj.group(1)
+               self.report_title(video_title)
+               # Step 2, Stimulate clicking the image box to launch video
+               pattern = '"(https\://plus\.google\.com/photos/.*?)",,"image/jpeg","video"\]'
+               mobj = re.search(pattern, webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract video page URL')
+               video_page = mobj.group(1)
+               request = urllib2.Request(video_page)
+               try:
+                       webpage = urllib2.urlopen(request).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
+                       return
+               self.report_extract_vid_page(video_page)
+               # Extract video links on video page
+               """Extract video links of all sizes"""
+               pattern = '\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"'
+               mobj = re.findall(pattern, webpage)
 -              video_url = unicode(video_url, "unicode_escape").encode("utf8")
++              if len(mobj) == 0:
+                       self._downloader.trouble(u'ERROR: unable to extract video links')
+               # Sort in resolution
+               links = sorted(mobj)
+               # Choose the lowest of the sort, i.e. highest resolution
+               video_url = links[-1]
+               # Only get the url. The resolution part in the tuple has no use anymore
+               video_url = video_url[-1]
+               # Treat escaped \u0026 style hex
 -                      'url':          video_url.decode('utf-8'),
++              video_url = unicode(video_url, "unicode_escape")
+               return [{
+                       'id':           video_id.decode('utf-8'),
 -
++                      'url':          video_url,
+                       'uploader':     uploader.decode('utf-8'),
+                       'upload_date':  upload_date.decode('utf-8'),
+                       'title':        video_title.decode('utf-8'),
+                       'ext':          video_extension.decode('utf-8'),
+                       'format':       u'NA',
+                       'player_url':   None,
+               }]
diff --combined youtube_dl/__init__.py
index b21416dafbd8495fca65612c64b4e6f6dfda1405,fc8101f82600229b9d2671e7a7a01f98fa6624fd..15a3ec4cf8563f7d44b7c429d24e07ef7c92abbd
@@@ -19,7 -19,7 +19,7 @@@ __authors__  = 
        )
  
  __license__ = 'Public Domain'
 -__version__ = '2012.02.27'
 +__version__ = '2012.09.27'
  
  UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
  UPDATE_URL_VERSION = 'https://raw.github.com/rg3/youtube-dl/master/LATEST_VERSION'
@@@ -186,18 -186,16 +186,18 @@@ def parseOpts()
        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)
 +                      dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
        general.add_option('--dump-user-agent',
                        action='store_true', dest='dump_user_agent',
                        help='display the current browser identification', default=False)
 +      general.add_option('--user-agent',
 +                      dest='user_agent', help='specify a custom user agent', metavar='UA')
        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)
 +                      dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is %default)', 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)')
                        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. 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.')
 +                      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), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id 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',
                        help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
        postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
                        help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default')
 -      postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
 -                      help='ffmpeg/avconv audio bitrate specification, 128k by default')
 +      postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
 +                      help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
        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')
  
@@@ -353,8 -351,7 +353,9 @@@ def gen_extractors()
                MixcloudIE(),
                StanfordOpenClassroomIE(),
                MTVIE(),
 +              YoukuIE(),
 +              XNXXIE(),
+               GooglePlusIE(),
  
                GenericIE()
        ]
@@@ -372,9 -369,6 +373,9 @@@ def _real_main()
                                jar.load()
                except (IOError, OSError), err:
                        sys.exit(u'ERROR: unable to open cookie file')
 +      # Set user agent
 +      if opts.user_agent is not None:
 +              std_headers['User-Agent'] = opts.user_agent
  
        # Dump user agent
        if opts.dump_user_agent:
        if opts.extractaudio:
                if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a', 'wav']:
                        parser.error(u'invalid audio format specified')
 +      if opts.audioquality:
 +              opts.audioquality = opts.audioquality.strip('k').strip('K')
 +              if not opts.audioquality.isdigit():
 +                      parser.error(u'invalid audio quality specified')
  
        # File downloader
        fd = FileDownloader({