Simplify simplify_title
[youtube-dl.git] / youtube-dl
index 2e29bad..d4eadc9 100755 (executable)
@@ -12,6 +12,8 @@ __author__  = (
        'Rogério Brito',
        'Philipp Hagemeister',
        'Sören Schulze',
+       'Kevin Ngo',
+       'Ori Avtalion',
        )
 
 __license__ = 'Public Domain'
@@ -77,8 +79,6 @@ std_headers = {
        'Accept-Language': 'en-us,en;q=0.5',
 }
 
-simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii')
-
 try:
        import json
 except ImportError: # Python <2.6, use trivialjson (https://github.com/phihag/trivialjson):
@@ -277,6 +277,8 @@ def timeconvert(timestr):
                timestamp = email.utils.mktime_tz(timetuple)
        return timestamp
 
+def _simplify_title(title):
+       return re.sub(ur'[^\w\d_\-]+', u'_', title).strip(u'_')
 
 class DownloadError(Exception):
        """Download Error exception.
@@ -1289,8 +1291,7 @@ class YoutubeIE(InfoExtractor):
                video_title = sanitize_title(video_title)
 
                # simplified title
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
-               simple_title = simple_title.strip(ur'_')
+               simple_title = _simplify_title(video_title)
 
                # thumbnail image
                if 'thumbnail_url' not in video_info:
@@ -1560,9 +1561,6 @@ class DailymotionIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                # Extract id and simplified title from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1651,9 +1649,6 @@ class GoogleIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[video.google] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                # Extract id from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1697,7 +1692,7 @@ class GoogleIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+               simple_title = _simplify_title(video_title)
 
                # Extract video description
                mobj = re.search(r'<span id=short-desc-content>([^<]*)</span>', webpage)
@@ -1758,9 +1753,6 @@ class PhotobucketIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[photobucket] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                # Extract id from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1799,7 +1791,7 @@ class PhotobucketIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+               simple_title = _simplify_title(vide_title)
 
                video_uploader = mobj.group(2).decode('utf-8')
 
@@ -1840,9 +1832,6 @@ class YahooIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[video.yahoo] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url, new_video=True):
                # Extract ID from URL
                mobj = re.match(self._VALID_URL, url)
@@ -1896,7 +1885,7 @@ class YahooIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: unable to extract video title')
                        return
                video_title = mobj.group(1).decode('utf-8')
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+               simple_title = _simplify_title(video_title)
 
                mobj = re.search(r'<h2 class="ti-5"><a href="http://video\.yahoo\.com/(people|profile)/[0-9]+" beacon=".*">(.*)</a></h2>', webpage)
                if mobj is None:
@@ -1993,9 +1982,6 @@ class VimeoIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[vimeo] %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)
@@ -2027,7 +2013,7 @@ class VimeoIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: unable to extract video title')
                        return
                video_title = mobj.group(1).decode('utf-8')
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+               simple_title = _simple_title(video_title)
 
                # Extract uploader
                mobj = re.search(r'<uploader_url>http://vimeo.com/(.*?)</uploader_url>', webpage)
@@ -2118,9 +2104,6 @@ class GenericIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[generic] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                # At this point we have a new video
                self._downloader.increment_downloads()
@@ -2174,7 +2157,7 @@ class GenericIE(InfoExtractor):
                        return
                video_title = mobj.group(1).decode('utf-8')
                video_title = sanitize_title(video_title)
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+               simple_title = _simplify_title(video_title)
 
                # video uploader is domain name
                mobj = re.match(r'(?:https?://)?([^/]*)/.*', url)
@@ -2470,7 +2453,7 @@ class YahooSearchIE(InfoExtractor):
 class YoutubePlaylistIE(InfoExtractor):
        """Information Extractor for YouTube playlists."""
 
-       _VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
+       _VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
        _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
        _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
        _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
@@ -2514,7 +2497,8 @@ class YoutubePlaylistIE(InfoExtractor):
 
                while True:
                        self.report_download_page(playlist_id, pagenum)
-                       request = urllib2.Request(self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum))
+                       url = self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum)
+                       request = urllib2.Request(url)
                        try:
                                page = urllib2.urlopen(request).read()
                        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
@@ -2548,7 +2532,7 @@ class YoutubeUserIE(InfoExtractor):
        _TEMPLATE_URL = 'http://gdata.youtube.com/feeds/api/users/%s'
        _GDATA_PAGE_SIZE = 50
        _GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d'
-       _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
+       _VIDEO_INDICATOR = r'/watch\?v=(.+?)[\<&]'
        _youtube_ie = None
        IE_NAME = u'youtube:user'
 
@@ -2646,9 +2630,6 @@ class DepositFilesIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[DepositFiles] %s: Extracting information' % file_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                # At this point we have a new file
                self._downloader.increment_downloads()
@@ -2846,9 +2827,7 @@ class FacebookIE(InfoExtractor):
                video_title = video_title.decode('utf-8')
                video_title = sanitize_title(video_title)
 
-               # simplified title
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
-               simple_title = simple_title.strip(ur'_')
+               simple_title = _simplify_title(video_title)
 
                # thumbnail image
                if 'thumbnail' not in video_info:
@@ -2939,11 +2918,6 @@ class BlipTVIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[%s] %s: Direct download detected' % (self.IE_NAME, title))
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
-
        def _real_extract(self, url):
                mobj = re.match(self._VALID_URL, url)
                if mobj is None:
@@ -2969,7 +2943,7 @@ class BlipTVIE(InfoExtractor):
                                        'id': title,
                                        'url': url,
                                        'title': title,
-                                       'stitle': self._simplify_title(title),
+                                       'stitle': _simplify_title(title),
                                        'ext': ext,
                                        'urlhandle': urlh
                                }
@@ -3003,7 +2977,7 @@ class BlipTVIE(InfoExtractor):
                                        'uploader': data['display_name'],
                                        'upload_date': upload_date,
                                        'title': data['title'],
-                                       'stitle': self._simplify_title(data['title']),
+                                       'stitle': _simplify_title(data['title']),
                                        'ext': ext,
                                        'format': data['media']['mimeType'],
                                        'thumbnail': data['thumbnailUrl'],
@@ -3039,9 +3013,6 @@ class MyVideoIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[myvideo] %s: Extracting information' % video_id)
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self,url):
                mobj = re.match(self._VALID_URL, url)
                if mobj is None:
@@ -3049,10 +3020,6 @@ class MyVideoIE(InfoExtractor):
                        return
 
                video_id = mobj.group(1)
-               simple_title = mobj.group(2).decode('utf-8')
-               # should actually not be necessary
-               simple_title = sanitize_title(simple_title)
-               simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', simple_title)
 
                # Get video webpage
                request = urllib2.Request('http://www.myvideo.de/watch/%s' % video_id)
@@ -3079,6 +3046,8 @@ class MyVideoIE(InfoExtractor):
                video_title = mobj.group(1)
                video_title = sanitize_title(video_title)
 
+               simple_title = _simplify_title(video_title)
+
                try:
                        self._downloader.process_info({
                                'id':           video_id,
@@ -3112,11 +3081,6 @@ class ComedyCentralIE(InfoExtractor):
        def report_player_url(self, episode_id):
                self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_id)
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
-
        def _real_extract(self, url):
                mobj = re.match(self._VALID_URL, url)
                if mobj is None:
@@ -3247,11 +3211,6 @@ class EscapistIE(InfoExtractor):
        def report_config_download(self, showName):
                self._downloader.to_screen(u'[escapist] %s: Downloading configuration' % showName)
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
-
        def _real_extract(self, url):
                htmlParser = HTMLParser.HTMLParser()
 
@@ -3304,7 +3263,7 @@ class EscapistIE(InfoExtractor):
                        'uploader': showName,
                        'upload_date': None,
                        'title': showName,
-                       'stitle': self._simplify_title(showName),
+                       'stitle': _simplify_title(showName),
                        'ext': 'flv',
                        'format': 'flv',
                        'thumbnail': imgUrl,
@@ -3332,11 +3291,6 @@ class CollegeHumorIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
-
        def _real_extract(self, url):
                htmlParser = HTMLParser.HTMLParser()
 
@@ -3378,7 +3332,7 @@ class CollegeHumorIE(InfoExtractor):
                        videoNode = mdoc.findall('./video')[0]
                        info['description'] = videoNode.findall('./description')[0].text
                        info['title'] = videoNode.findall('./caption')[0].text
-                       info['stitle'] = self._simplify_title(info['title'])
+                       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]
@@ -3409,11 +3363,6 @@ class XVideosIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
 
-       def _simplify_title(self, title):
-               res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
-               res = res.strip(ur'_')
-               return res
-
        def _real_extract(self, url):
                htmlParser = HTMLParser.HTMLParser()
 
@@ -3467,7 +3416,7 @@ class XVideosIE(InfoExtractor):
                        'uploader': None,
                        'upload_date': None,
                        'title': video_title,
-                       'stitle': self._simplify_title(video_title),
+                       'stitle': _simplify_title(video_title),
                        'ext': 'flv',
                        'format': 'flv',
                        'thumbnail': video_thumbnail,
@@ -3504,9 +3453,6 @@ class SoundcloudIE(InfoExtractor):
                """Report information extraction."""
                self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
 
-       def _real_initialize(self):
-               return
-
        def _real_extract(self, url):
                htmlParser = HTMLParser.HTMLParser()
 
@@ -3533,7 +3479,7 @@ class SoundcloudIE(InfoExtractor):
                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)   
+               mobj = re.search('"uid":"([\w\d]+?)".*?stream_token=([\w\d]+)', webpage)
                if mobj:
                        video_id = mobj.group(1)
                        stream_token = mobj.group(2)
@@ -3582,6 +3528,86 @@ class SoundcloudIE(InfoExtractor):
                        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 PostProcessor(object):
        """Post Processor class.
 
@@ -3978,12 +4004,13 @@ def gen_extractors():
                EscapistIE(),
                CollegeHumorIE(),
                XVideosIE(),
-        SoundcloudIE(),
+               SoundcloudIE(),
+               InfoQIE(),
 
                GenericIE()
        ]
 
-def main():
+def _real_main():
        parser, opts, args = parseOpts()
 
        # Open appropriate CookieJar
@@ -4143,10 +4170,9 @@ def main():
 
        sys.exit(retcode)
 
-
-if __name__ == '__main__':
+def main():
        try:
-               main()
+               _real_main()
        except DownloadError:
                sys.exit(1)
        except SameFileError:
@@ -4154,4 +4180,7 @@ if __name__ == '__main__':
        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: