diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | test/test_youtube_lists.py | 8 | ||||
| -rw-r--r-- | youtube_dl/YoutubeDL.py | 1 | ||||
| -rw-r--r-- | youtube_dl/__init__.py | 14 | ||||
| -rw-r--r-- | youtube_dl/extractor/dailymotion.py | 43 | ||||
| -rw-r--r-- | youtube_dl/extractor/francetv.py | 16 | ||||
| -rw-r--r-- | youtube_dl/extractor/ign.py | 54 | ||||
| -rw-r--r-- | youtube_dl/extractor/youtube.py | 29 | ||||
| -rw-r--r-- | youtube_dl/utils.py | 6 | ||||
| -rw-r--r-- | youtube_dl/version.py | 2 | 
10 files changed, 144 insertions, 35 deletions
| @@ -31,8 +31,9 @@ which means you can modify it, redistribute it or use it however you like.      --proxy URL                Use the specified HTTP/HTTPS proxy      --no-check-certificate     Suppress HTTPS certificate validation.      --cache-dir None           Location in the filesystem where youtube-dl can -                               store downloaded information permanently. -                               ~/.youtube-dl/cache by default +                               store downloaded information permanently. By +                               default $XDG_CACHE_HOME/youtube-dl or ~/.cache +                               /youtube-dl .      --no-cache-dir             Disable filesystem caching  ## Video Selection: @@ -50,6 +51,7 @@ which means you can modify it, redistribute it or use it however you like.      --date DATE                download only videos uploaded in this date      --datebefore DATE          download only videos uploaded before this date      --dateafter DATE           download only videos uploaded after this date +    --no-playlist              download only the currently playing video  ## Download Options:      -r, --rate-limit LIMIT     maximum download rate (e.g. 50k or 44.6m) diff --git a/test/test_youtube_lists.py b/test/test_youtube_lists.py index dd9e292b0..53e65816d 100644 --- a/test/test_youtube_lists.py +++ b/test/test_youtube_lists.py @@ -27,6 +27,14 @@ class TestYoutubeLists(unittest.TestCase):          ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]          self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE']) +    def test_youtube_playlist_noplaylist(self): +        dl = FakeYDL() +        dl.params['noplaylist'] = True +        ie = YoutubePlaylistIE(dl) +        result = ie.extract('https://www.youtube.com/watch?v=FXxLjLQi3Fg&list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re') +        self.assertEqual(result['_type'], 'url') +        self.assertEqual(YoutubeIE()._extract_id(result['url']), 'FXxLjLQi3Fg') +      def test_issue_673(self):          dl = FakeYDL()          ie = YoutubePlaylistIE(dl) diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 44a272e7e..2503fd09b 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -83,6 +83,7 @@ class YoutubeDL(object):      skip_download:     Skip the actual download of the video file      cachedir:          Location of the cache files in the filesystem.                         None to disable filesystem cache. +    noplaylist:        Download single video instead of a playlist if in doubt.      The following parameters are not used by YoutubeDL itself, they are used by      the FileDownloader: diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 28a7bdd92..03df835f2 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -168,8 +168,8 @@ def parseOpts(overrideArguments=None):      general.add_option('--proxy', dest='proxy', default=None, help='Use the specified HTTP/HTTPS proxy', metavar='URL')      general.add_option('--no-check-certificate', action='store_true', dest='no_check_certificate', default=False, help='Suppress HTTPS certificate validation.')      general.add_option( -        '--cache-dir', dest='cachedir', default=u'~/.youtube-dl/cache', -        help='Location in the filesystem where youtube-dl can store downloaded information permanently. %default by default') +        '--cache-dir', dest='cachedir', default=get_cachedir(), +        help='Location in the filesystem where youtube-dl can store downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl or ~/.cache/youtube-dl .')      general.add_option(          '--no-cache-dir', action='store_const', const=None, dest='cachedir',          help='Disable filesystem caching') @@ -187,6 +187,7 @@ def parseOpts(overrideArguments=None):      selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None)      selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None)      selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None) +    selection.add_option('--no-playlist', action='store_true', dest='noplaylist', help='download only the currently playing video', default=False)      authentication.add_option('-u', '--username', @@ -369,9 +370,13 @@ def parseOpts(overrideArguments=None):      else:          xdg_config_home = os.environ.get('XDG_CONFIG_HOME')          if xdg_config_home: -            userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf') +            userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config') +            if not os.path.isfile(userConfFile): +                userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')          else: -            userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf') +            userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl', 'config') +            if not os.path.isfile(userConfFile): +                userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')          systemConf = _readOptions('/etc/youtube-dl.conf')          userConf = _readOptions(userConfFile)          commandLineConf = sys.argv[1:] @@ -599,6 +604,7 @@ def _real_main(argv=None):          'progress_with_newline': opts.progress_with_newline,          'playliststart': opts.playliststart,          'playlistend': opts.playlistend, +        'noplaylist': opts.noplaylist,          'logtostderr': opts.outtmpl == '-',          'consoletitle': opts.consoletitle,          'nopart': opts.nopart, diff --git a/youtube_dl/extractor/dailymotion.py b/youtube_dl/extractor/dailymotion.py index 3f012aedc..259806f38 100644 --- a/youtube_dl/extractor/dailymotion.py +++ b/youtube_dl/extractor/dailymotion.py @@ -27,15 +27,31 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):      _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/(?:embed/)?video/([^/]+)'      IE_NAME = u'dailymotion' -    _TEST = { -        u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech', -        u'file': u'x33vw9.mp4', -        u'md5': u'392c4b85a60a90dc4792da41ce3144eb', -        u'info_dict': { -            u"uploader": u"Amphora Alex and Van .",  -            u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\"" -        } -    } +    _TESTS = [ +        { +            u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech', +            u'file': u'x33vw9.mp4', +            u'md5': u'392c4b85a60a90dc4792da41ce3144eb', +            u'info_dict': { +                u"uploader": u"Amphora Alex and Van .",  +                u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\"" +            } +        }, +        # Vevo video +        { +            u'url': u'http://www.dailymotion.com/video/x149uew_katy-perry-roar-official_musi', +            u'file': u'USUV71301934.mp4', +            u'info_dict': { +                u'title': u'Roar (Official)', +                u'uploader': u'Katy Perry', +                u'upload_date': u'20130905', +            }, +            u'params': { +                u'skip_download': True, +            }, +            u'skip': u'VEVO is only available in some countries', +        }, +    ]      def _real_extract(self, url):          # Extract id and simplified title from URL @@ -53,6 +69,15 @@ class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor):          # Extract URL, uploader and title from webpage          self.report_extraction(video_id) +        # It may just embed a vevo video: +        m_vevo = re.search( +            r'<link rel="video_src" href="[^"]*?vevo.com[^"]*?videoId=(?P<id>[\w]*)', +            webpage) +        if m_vevo is not None: +            vevo_id = m_vevo.group('id') +            self.to_screen(u'Vevo video detected: %s' % vevo_id) +            return self.url_result(u'vevo:%s' % vevo_id, ie='Vevo') +          video_uploader = self._search_regex([r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a>',                                               # Looking for official user                                               r'<(?:span|a) .*?rel="author".*?>([^<]+?)</'], diff --git a/youtube_dl/extractor/francetv.py b/youtube_dl/extractor/francetv.py index b1530e549..461dac8ef 100644 --- a/youtube_dl/extractor/francetv.py +++ b/youtube_dl/extractor/francetv.py @@ -70,7 +70,11 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):  class France2IE(FranceTVBaseInfoExtractor):      IE_NAME = u'france2.fr' -    _VALID_URL = r'https?://www\.france2\.fr/emissions/.*?/videos/(?P<id>\d+)' +    _VALID_URL = r'''(?x)https?://www\.france2\.fr/ +        (?: +            emissions/.*?/videos/(?P<id>\d+) +        |   emission/(?P<key>[^/?]+) +        )'''      _TEST = {          u'url': u'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104', @@ -86,7 +90,15 @@ class France2IE(FranceTVBaseInfoExtractor):      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url) -        video_id = mobj.group('id') +        if mobj.group('key'): +            webpage = self._download_webpage(url, mobj.group('key')) +            video_id = self._html_search_regex( +                r'''(?x)<div\s+class="video-player">\s* +                    <a\s+href="http://videos.francetv.fr/video/([0-9]+)"\s+ +                    class="francetv-video-player">''', +                webpage, u'video ID') +        else: +            video_id = mobj.group('id')          return self._extract_video(video_id) diff --git a/youtube_dl/extractor/ign.py b/youtube_dl/extractor/ign.py index b1c84278a..c52146f7d 100644 --- a/youtube_dl/extractor/ign.py +++ b/youtube_dl/extractor/ign.py @@ -13,7 +13,7 @@ class IGNIE(InfoExtractor):      Some videos of it.ign.com are also supported      """ -    _VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles)(/.+)?/(?P<name_or_id>.+)' +    _VALID_URL = r'https?://.+?\.ign\.com/(?P<type>videos|show_videos|articles|(?:[^/]*/feature))(/.+)?/(?P<name_or_id>.+)'      IE_NAME = u'ign.com'      _CONFIG_URL_TEMPLATE = 'http://www.ign.com/videos/configs/id/%s.config' @@ -21,15 +21,39 @@ class IGNIE(InfoExtractor):                         r'id="my_show_video">.*?<p>(.*?)</p>',                         ] -    _TEST = { -        u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review', -        u'file': u'8f862beef863986b2785559b9e1aa599.mp4', -        u'md5': u'eac8bdc1890980122c3b66f14bdd02e9', -        u'info_dict': { -            u'title': u'The Last of Us Review', -            u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c', -        } -    } +    _TESTS = [ +        { +            u'url': u'http://www.ign.com/videos/2013/06/05/the-last-of-us-review', +            u'file': u'8f862beef863986b2785559b9e1aa599.mp4', +            u'md5': u'eac8bdc1890980122c3b66f14bdd02e9', +            u'info_dict': { +                u'title': u'The Last of Us Review', +                u'description': u'md5:c8946d4260a4d43a00d5ae8ed998870c', +            } +        }, +        { +            u'url': u'http://me.ign.com/en/feature/15775/100-little-things-in-gta-5-that-will-blow-your-mind', +            u'playlist': [ +                { +                    u'file': u'5ebbd138523268b93c9141af17bec937.mp4', +                    u'info_dict': { +                        u'title': u'GTA 5 Video Review', +                        u'description': u'Rockstar drops the mic on this generation of games. Watch our review of the masterly Grand Theft Auto V.', +                    }, +                }, +                { +                    u'file': u'638672ee848ae4ff108df2a296418ee2.mp4', +                    u'info_dict': { +                        u'title': u'GTA 5\'s Twisted Beauty in Super Slow Motion', +                        u'description': u'The twisted beauty of GTA 5 in stunning slow motion.', +                    }, +                }, +            ], +            u'params': { +                u'skip_download': True, +            }, +        }, +    ]      def _find_video_id(self, webpage):          res_id = [r'data-video-id="(.+?)"', @@ -46,6 +70,13 @@ class IGNIE(InfoExtractor):          if page_type == 'articles':              video_url = self._search_regex(r'var videoUrl = "(.+?)"', webpage, u'video url')              return self.url_result(video_url, ie='IGN') +        elif page_type != 'video': +            multiple_urls = re.findall( +                '<param name="flashvars" value="[^"]*?url=(https?://www\.ign\.com/videos/.*?)["&]', +                webpage) +            if multiple_urls: +                return [self.url_result(u, ie='IGN') for u in multiple_urls] +          video_id = self._find_video_id(webpage)          result = self._get_video_info(video_id)          description = self._html_search_regex(self._DESCRIPTION_RE, @@ -87,6 +118,9 @@ class OneUPIE(IGNIE):          }      } +    # Override IGN tests +    _TESTS = [] +      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url)          id = mobj.group('name_or_id') diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 53f13b516..39ff33290 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -23,9 +23,11 @@ from ..utils import (      compat_urllib_error,      compat_urllib_parse,      compat_urllib_request, +    compat_urlparse,      compat_str,      clean_html, +    get_cachedir,      get_element_by_id,      ExtractorError,      unescapeHTML, @@ -420,8 +422,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):          # Read from filesystem cache          func_id = '%s_%s_%d' % (player_type, player_id, slen)          assert os.path.basename(func_id) == func_id -        cache_dir = self._downloader.params.get('cachedir', -                                                u'~/.youtube-dl/cache') +        cache_dir = get_cachedir(self._downloader.params)          cache_enabled = cache_dir is not None          if cache_enabled: @@ -1086,7 +1087,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):          elif len(s) == 83:              return s[80:63:-1] + s[0] + s[62:0:-1] + s[63]          elif len(s) == 82: -            return s[80:73:-1] + s[81] + s[72:54:-1] + s[2] + s[53:43:-1] + s[0] + s[42:2:-1] + s[43] + s[1] + s[54] +            return s[80:37:-1] + s[7] + s[36:7:-1] + s[0] + s[6:0:-1] + s[37]          elif len(s) == 81:              return s[56] + s[79:56:-1] + s[41] + s[55:41:-1] + s[80] + s[40:34:-1] + s[0] + s[33:29:-1] + s[34] + s[28:9:-1] + s[29] + s[8:0:-1] + s[9]          elif len(s) == 80: @@ -1333,9 +1334,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):              self._downloader.report_warning(u'unable to extract uploader nickname')          # title -        if 'title' not in video_info: -            raise ExtractorError(u'Unable to extract video title') -        video_title = compat_urllib_parse.unquote_plus(video_info['title'][0]) +        if 'title' in video_info: +            video_title = compat_urllib_parse.unquote_plus(video_info['title'][0]) +        else: +            self._downloader.report_warning(u'Unable to extract video title') +            video_title = u'_'          # thumbnail image          # We try first to get a high quality image: @@ -1390,6 +1393,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):              args = info['args']              # Easy way to know if the 's' value is in url_encoded_fmt_stream_map              # this signatures are encrypted +            if 'url_encoded_fmt_stream_map' not in args: +                raise ValueError(u'No stream_map present')  # caught below              m_s = re.search(r'[&,]s=', args['url_encoded_fmt_stream_map'])              if m_s is not None:                  self.to_screen(u'%s: Encrypted signatures detected.' % video_id) @@ -1523,9 +1528,19 @@ class YoutubePlaylistIE(InfoExtractor):          mobj = re.match(self._VALID_URL, url, re.VERBOSE)          if mobj is None:              raise ExtractorError(u'Invalid URL: %s' % url) +        playlist_id = mobj.group(1) or mobj.group(2) + +        # Check if it's a video-specific URL +        query_dict = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query) +        if 'v' in query_dict: +            video_id = query_dict['v'][0] +            if self._downloader.params.get('noplaylist'): +                self.to_screen(u'Downloading just video %s because of --no-playlist' % video_id) +                return self.url_result('https://www.youtube.com/watch?v=' + video_id, 'Youtube') +            else: +                self.to_screen(u'Downloading playlist PL%s - add --no-playlist to just download video %s' % (playlist_id, video_id))          # Download playlist videos from API -        playlist_id = mobj.group(1) or mobj.group(2)          videos = []          for page_num in itertools.count(1): diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 201ed255d..f5f9cde99 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -824,3 +824,9 @@ def intlist_to_bytes(xs):          return ''.join([chr(x) for x in xs])      else:          return bytes(xs) + + +def get_cachedir(params={}): +    cache_root = os.environ.get('XDG_CACHE_HOME', +                                os.path.expanduser('~/.cache')) +    return params.get('cachedir', os.path.join(cache_root, 'youtube-dl')) diff --git a/youtube_dl/version.py b/youtube_dl/version.py index e3e5d5538..e773e82da 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2013.09.29' +__version__ = '2013.10.04' | 
