Merge PR #422 from 'kevinamadeus/master'
[youtube-dl] / youtube_dl / InfoExtractors.py
index 6442888d88eb3d097d92ee792b1fcbb7b343a230..f97611cb9b4ab681eb21f760db4c71ea1b2da98a 100644 (file)
@@ -97,7 +97,25 @@ class InfoExtractor(object):
 class YoutubeIE(InfoExtractor):
        """Information extractor for youtube.com."""
 
-       _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|tube\.majestyc\.net/)(?!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'
@@ -136,6 +154,10 @@ class YoutubeIE(InfoExtractor):
        }       
        IE_NAME = u'youtube'
 
+       def suitable(self, url):
+               """Receives a URL and returns True if suitable for this IE."""
+               return re.match(self._VALID_URL, url, re.VERBOSE) is not None
+
        def report_lang(self):
                """Report attempt to set language."""
                self._downloader.to_screen(u'[youtube] Setting language')
@@ -270,7 +292,7 @@ class YoutubeIE(InfoExtractor):
                        url = 'http://www.youtube.com/' + urllib.unquote(mobj.group(1)).lstrip('/')
 
                # Extract video id from URL
-               mobj = re.match(self._VALID_URL, url)
+               mobj = re.match(self._VALID_URL, url, re.VERBOSE)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
@@ -594,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):
@@ -615,9 +637,9 @@ class DailymotionIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
 
-               video_id = mobj.group(1)
+               video_id = mobj.group(1).split('_')[0].split('?')[0]
 
-               video_extension = 'flv'
+               video_extension = 'mp4'
 
                # Retrieve video webpage to extract further information
                request = urllib2.Request(url)
@@ -631,20 +653,23 @@ class DailymotionIE(InfoExtractor):
 
                # 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('\\', '')
+               video_url = urllib.unquote(mobj.group(1)).replace('\\/', '/')
 
-               # if needed add http://www.dailymotion.com/ if relative URL
-
-               video_url = mediaURL
+               # TODO: support choosing qualities
 
                mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)
                if mobj is None:
@@ -658,11 +683,16 @@ class DailymotionIE(InfoExtractor):
                        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',
@@ -3144,3 +3174,127 @@ class XNXXIE(InfoExtractor):
                                'player_url': None}
 
                return [info]
+
+
+class GooglePlusIE(InfoExtractor):
+       """Information extractor for plus.google.com."""
+
+       _VALID_URL = r'(?:https://)?plus\.google\.com/(?:\w+/)*?(\d+)/posts/(\w+)'
+       IE_NAME = u'plus.google'
+
+       def __init__(self, downloader=None):
+               InfoExtractor.__init__(self, downloader)
+
+       def report_extract_entry(self, url):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Downloading entry: %s' % url.decode('utf-8'))
+
+       def report_date(self, upload_date):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Entry date: %s' % upload_date)
+
+       def report_uploader(self, uploader):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Uploader: %s' % uploader.decode('utf-8'))
+
+       def report_title(self, video_title):
+               """Report downloading extry"""
+               self._downloader.to_screen(u'[plus.google] Title: %s' % video_title.decode('utf-8'))
+
+       def report_extract_vid_page(self, video_page):
+               """Report information extraction."""
+               self._downloader.to_screen(u'[plus.google] Extracting video page: %s' % video_page.decode('utf-8'))
+
+       def _real_extract(self, url):
+               # Extract id from URL
+               mobj = re.match(self._VALID_URL, url)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
+                       return
+
+               post_url = mobj.group(0)
+               video_id = mobj.group(2)
+
+               video_extension = 'flv'
+
+               # Step 1, Retrieve post webpage to extract further information
+               self.report_extract_entry(post_url)
+               request = urllib2.Request(post_url)
+               try:
+                       webpage = urllib2.urlopen(request).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: Unable to retrieve entry webpage: %s' % str(err))
+                       return
+
+               # Extract update date
+               upload_date = u'NA'
+               pattern = 'title="Timestamp">(.*?)</a>'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       upload_date = mobj.group(1)
+                       # Convert timestring to a format suitable for filename
+                       upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d")
+                       upload_date = upload_date.strftime('%Y%m%d')
+               self.report_date(upload_date)
+
+               # Extract uploader
+               uploader = u'NA'
+               pattern = r'rel\="author".*?>(.*?)</a>'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       uploader = mobj.group(1)
+               self.report_uploader(uploader)
+
+               # Extract title
+               # Get the first line for title
+               video_title = u'NA'
+               pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]'
+               mobj = re.search(pattern, webpage)
+               if mobj:
+                       video_title = mobj.group(1)
+               self.report_title(video_title)
+
+               # Step 2, Stimulate clicking the image box to launch video
+               pattern = '"(https\://plus\.google\.com/photos/.*?)",,"image/jpeg","video"\]'
+               mobj = re.search(pattern, webpage)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: unable to extract video page URL')
+
+               video_page = mobj.group(1)
+               request = urllib2.Request(video_page)
+               try:
+                       webpage = urllib2.urlopen(request).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
+                       return
+               self.report_extract_vid_page(video_page)
+
+
+               # Extract video links on video page
+               """Extract video links of all sizes"""
+               pattern = '\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"'
+               mobj = re.findall(pattern, webpage)
+               if len(mobj) == 0:
+                       self._downloader.trouble(u'ERROR: unable to extract video links')
+
+               # Sort in resolution
+               links = sorted(mobj)
+
+               # Choose the lowest of the sort, i.e. highest resolution
+               video_url = links[-1]
+               # Only get the url. The resolution part in the tuple has no use anymore
+               video_url = video_url[-1]
+               # Treat escaped \u0026 style hex
+               video_url = unicode(video_url, "unicode_escape")
+
+
+               return [{
+                       'id':           video_id.decode('utf-8'),
+                       'url':          video_url,
+                       'uploader':     uploader.decode('utf-8'),
+                       'upload_date':  upload_date.decode('utf-8'),
+                       'title':        video_title.decode('utf-8'),
+                       'ext':          video_extension.decode('utf-8'),
+                       'format':       u'NA',
+                       'player_url':   None,
+               }]