diff options
| -rw-r--r-- | test/test_all_urls.py | 14 | ||||
| -rw-r--r-- | test/test_youtube_lists.py | 13 | ||||
| -rwxr-xr-x | youtube_dl/InfoExtractors.py | 65 | 
3 files changed, 59 insertions, 33 deletions
| diff --git a/test/test_all_urls.py b/test/test_all_urls.py index 06de8e7b8..69717b3fc 100644 --- a/test/test_all_urls.py +++ b/test/test_all_urls.py @@ -11,12 +11,18 @@ from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE  class TestAllURLsMatching(unittest.TestCase):      def test_youtube_playlist_matching(self): -        self.assertTrue(YoutubePlaylistIE().suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) -        self.assertTrue(YoutubePlaylistIE().suitable(u'PL63F0C78739B09958')) -        self.assertFalse(YoutubePlaylistIE().suitable(u'PLtS2H6bU1M')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585 +        self.assertTrue(YoutubePlaylistIE.suitable(u'PL63F0C78739B09958')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')) +        self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668 +        self.assertFalse(YoutubePlaylistIE.suitable(u'PLtS2H6bU1M'))      def test_youtube_matching(self): -        self.assertTrue(YoutubeIE().suitable(u'PLtS2H6bU1M')) +        self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M')) +        self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668      def test_youtube_extract(self):          self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') diff --git a/test/test_youtube_lists.py b/test/test_youtube_lists.py index 69b0f4447..7055d5a7a 100644 --- a/test/test_youtube_lists.py +++ b/test/test_youtube_lists.py @@ -41,6 +41,18 @@ class TestYoutubeLists(unittest.TestCase):          self.assertEqual(map(lambda x: YoutubeIE()._extract_id(x[0]), DL.result),              [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE' ]) +        #661 +        DL = FakeDownloader() +        IE = YoutubePlaylistIE(DL) +        IE.extract('PLMCmkNmxw6Z9eduM7BZjSEh7HiU543Ig0') +        self.assertTrue(len(DL.result) > 20) + +        #673 +        DL = FakeDownloader() +        IE = YoutubePlaylistIE(DL) +        IE.extract('PLBB231211A4F62143') +        self.assertTrue(len(DL.result) > 40) +      def test_youtube_playlist_long(self):          DL = FakeDownloader()          IE = YoutubePlaylistIE(DL) @@ -48,6 +60,7 @@ class TestYoutubeLists(unittest.TestCase):          self.assertTrue(len(DL.result) >= 799)      def test_youtube_playlist_with_deleted(self): +        #651          DL = FakeDownloader()          IE = YoutubePlaylistIE(DL)          IE.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC') diff --git a/youtube_dl/InfoExtractors.py b/youtube_dl/InfoExtractors.py index 021579ce0..490f30491 100755 --- a/youtube_dl/InfoExtractors.py +++ b/youtube_dl/InfoExtractors.py @@ -74,13 +74,15 @@ class InfoExtractor(object):          self._ready = False          self.set_downloader(downloader) -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url) is not None +        return re.match(cls._VALID_URL, url) is not None -    def working(self): +    @classmethod +    def working(cls):          """Getter method for _WORKING.""" -        return self._WORKING +        return cls._WORKING      def initialize(self):          """Initializes an instance (authentication, etc).""" @@ -137,7 +139,6 @@ class YoutubeIE(InfoExtractor):                           (?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|                              tube\.majestyc\.net/)                             # the various hostnames, with wildcard subdomains                           (?:.*?\#/)?                                          # handle anchor (#/) redirect urls -                         (?!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 @@ -189,9 +190,11 @@ class YoutubeIE(InfoExtractor):      }      IE_NAME = u'youtube' -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url, re.VERBOSE) is not None +        if YoutubePlaylistIE.suitable(url): return False +        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None      def report_lang(self):          """Report attempt to set language.""" @@ -1668,17 +1671,17 @@ class YoutubePlaylistIE(InfoExtractor):                          (?:\w+\.)?                          youtube\.com/                          (?: -                           (?:course|view_play_list|my_playlists|artist|playlist) -                           \? .*? (p|a|list)= +                           (?:course|view_play_list|my_playlists|artist|playlist|watch) +                           \? (?:.*?&)*? (?:p|a|list)=                          |  user/.*?/user/                          |  p/                          |  user/.*?#[pg]/c/                          ) -                        (?:PL|EC)? -                     |PL|EC) -                     ([0-9A-Za-z-_]{10,}) -                     (?:/.*?/([0-9A-Za-z_-]+))? -                     .*""" +                        ((?:PL|EC|UU)?[0-9A-Za-z-_]{10,}) +                        .* +                     | +                        ((?:PL|EC|UU)[0-9A-Za-z-_]{10,}) +                     )"""      _TEMPLATE_URL = 'https://gdata.youtube.com/feeds/api/playlists/%s?max-results=%i&start-index=%i&v=2&alt=json'      _MAX_RESULTS = 50      IE_NAME = u'youtube:playlist' @@ -1686,9 +1689,10 @@ class YoutubePlaylistIE(InfoExtractor):      def __init__(self, downloader=None):          InfoExtractor.__init__(self, downloader) -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url, re.VERBOSE) is not None +        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None      def report_download_page(self, playlist_id, pagenum):          """Report attempt to download playlist page with given number.""" @@ -1701,13 +1705,8 @@ class YoutubePlaylistIE(InfoExtractor):              self._downloader.trouble(u'ERROR: invalid url: %s' % url)              return -        # Single video case -        if mobj.group(3) is not None: -            self._downloader.download([mobj.group(3)]) -            return -          # Download playlist videos from API -        playlist_id = mobj.group(2) +        playlist_id = mobj.group(1) or mobj.group(2)          page_num = 1          videos = [] @@ -1727,7 +1726,12 @@ class YoutubePlaylistIE(InfoExtractor):                  self._downloader.trouble(u'ERROR: Invalid JSON in API response: ' + compat_str(err))                  return -            videos += [(entry['yt$position']['$t'], entry['content']['src']) for entry in response['feed']['entry']] +            if not 'feed' in response or not 'entry' in response['feed']: +                self._downloader.trouble(u'ERROR: Got a malformed response from YouTube API') +                return +            videos += [ (entry['yt$position']['$t'], entry['content']['src']) +                        for entry in response['feed']['entry'] +                        if 'content' in entry ]              if len(response['feed']['entry']) < self._MAX_RESULTS:                  break @@ -2313,9 +2317,10 @@ class ComedyCentralIE(InfoExtractor):          '400': '384x216',      } -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url, re.VERBOSE) is not None +        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None      def report_extraction(self, episode_id):          self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id) @@ -3628,9 +3633,10 @@ class SteamIE(InfoExtractor):                  (?P<videoID>\d*)(?P<extra>\??) #For urltype == video we sometimes get the videoID                  """ -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url, re.VERBOSE) is not None +        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None      def _real_extract(self, url):          m = re.match(self._VALID_URL, url, re.VERBOSE) @@ -4004,9 +4010,10 @@ class TEDIE(InfoExtractor):                     /(?P<name>\w+) # Here goes the name and then ".html"                     ''' -    def suitable(self, url): +    @classmethod +    def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE.""" -        return re.match(self._VALID_URL, url, re.VERBOSE) is not None +        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None      def _real_extract(self, url):          m=re.match(self._VALID_URL, url, re.VERBOSE) | 
