diff options
| -rw-r--r-- | .gitignore | 8 | ||||
| -rw-r--r-- | test/parameters.json | 1 | ||||
| -rw-r--r-- | test/test_dailymotion_subtitles.py | 62 | ||||
| -rw-r--r-- | test/test_youtube_subtitles.py | 108 | ||||
| -rw-r--r-- | youtube_dl/YoutubeDL.py | 52 | ||||
| -rw-r--r-- | youtube_dl/__init__.py | 7 | ||||
| -rw-r--r-- | youtube_dl/extractor/common.py | 3 | ||||
| -rw-r--r-- | youtube_dl/extractor/dailymotion.py | 46 | ||||
| -rw-r--r-- | youtube_dl/extractor/subtitles.py | 75 | ||||
| -rw-r--r-- | youtube_dl/extractor/youtube.py | 196 | 
10 files changed, 314 insertions, 244 deletions
| diff --git a/.gitignore b/.gitignore index ca4e8f353..61cb6bc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,10 @@ youtube-dl.tar.gz  .coverage  cover/  updates_key.pem -*.egg-info
\ No newline at end of file +*.egg-info +*.srt +*.sbv +*.vtt +*.flv +*.mp4 +*.part diff --git a/test/parameters.json b/test/parameters.json index 96998b5c3..f042880ed 100644 --- a/test/parameters.json +++ b/test/parameters.json @@ -38,7 +38,6 @@      "writedescription": false,       "writeinfojson": true,       "writesubtitles": false, -    "onlysubtitles": false,      "allsubtitles": false,      "listssubtitles": false  } diff --git a/test/test_dailymotion_subtitles.py b/test/test_dailymotion_subtitles.py new file mode 100644 index 000000000..efc4e574f --- /dev/null +++ b/test/test_dailymotion_subtitles.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +import sys +import unittest +import json +import io +import hashlib + +# Allow direct execution +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from youtube_dl.extractor import DailymotionIE +from youtube_dl.utils import * +from helper import FakeYDL + +md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() + +class TestDailymotionSubtitles(unittest.TestCase): +    def setUp(self): +        self.DL = FakeYDL() +        self.url = 'http://www.dailymotion.com/video/xczg00' +    def getInfoDict(self): +        IE = DailymotionIE(self.DL) +        info_dict = IE.extract(self.url) +        return info_dict +    def getSubtitles(self): +        info_dict = self.getInfoDict() +        return info_dict[0]['subtitles'] +    def test_no_writesubtitles(self): +        subtitles = self.getSubtitles() +        self.assertEqual(subtitles, None) +    def test_subtitles(self): +        self.DL.params['writesubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f') +    def test_subtitles_lang(self): +        self.DL.params['writesubtitles'] = True +        self.DL.params['subtitleslang'] = 'fr' +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792') +    def test_allsubtitles(self): +        self.DL.params['allsubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(len(subtitles.keys()), 5) +    def test_list_subtitles(self): #ojo +        self.DL.params['listsubtitles'] = True +        info_dict = self.getInfoDict() +        self.assertEqual(info_dict, None) +    def test_automatic_captions(self): +        self.DL.params['writeautomaticsub'] = True +        self.DL.params['subtitleslang'] = 'en' +        subtitles = self.getSubtitles() +        self.assertTrue(len(subtitles.keys()) == 0) +    def test_nosubtitles(self): +        self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv' +        self.DL.params['allsubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(len(subtitles), 0) + +if __name__ == '__main__': +    unittest.main() diff --git a/test/test_youtube_subtitles.py b/test/test_youtube_subtitles.py index 86e09c9b1..e40243077 100644 --- a/test/test_youtube_subtitles.py +++ b/test/test_youtube_subtitles.py @@ -18,78 +18,58 @@ md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest()  class TestYoutubeSubtitles(unittest.TestCase):      def setUp(self): -        DL = FakeYDL() -        DL.params['allsubtitles'] = False -        DL.params['writesubtitles'] = False -        DL.params['subtitlesformat'] = 'srt' -        DL.params['listsubtitles'] = False -    def test_youtube_no_subtitles(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = False -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        subtitles = info_dict[0]['subtitles'] +        self.DL = FakeYDL() +        self.url = 'QRS8MkLhQmM' +    def getInfoDict(self): +        IE = YoutubeIE(self.DL) +        info_dict = IE.extract(self.url) +        return info_dict +    def getSubtitles(self): +        info_dict = self.getInfoDict() +        return info_dict[0]['subtitles']         +    def test_youtube_no_writesubtitles(self): +        self.DL.params['writesubtitles'] = False +        subtitles = self.getSubtitles()          self.assertEqual(subtitles, None)      def test_youtube_subtitles(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = True -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        sub = info_dict[0]['subtitles'][0] -        self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') -    def test_youtube_subtitles_it(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = True -        DL.params['subtitleslang'] = 'it' -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        sub = info_dict[0]['subtitles'][0] -        self.assertEqual(md5(sub[2]), '164a51f16f260476a05b50fe4c2f161d') -    def test_youtube_onlysubtitles(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = True -        DL.params['onlysubtitles'] = True -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        sub = info_dict[0]['subtitles'][0] -        self.assertEqual(md5(sub[2]), '4cd9278a35ba2305f47354ee13472260') +        self.DL.params['writesubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260') +    def test_youtube_subtitles_lang(self): +        self.DL.params['writesubtitles'] = True +        self.DL.params['subtitleslang'] = 'it' +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d')      def test_youtube_allsubtitles(self): -        DL = FakeYDL() -        DL.params['allsubtitles'] = True -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        subtitles = info_dict[0]['subtitles'] -        self.assertEqual(len(subtitles), 13) +        self.DL.params['allsubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(len(subtitles.keys()), 13)      def test_youtube_subtitles_sbv_format(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = True -        DL.params['subtitlesformat'] = 'sbv' -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        sub = info_dict[0]['subtitles'][0] -        self.assertEqual(md5(sub[2]), '13aeaa0c245a8bed9a451cb643e3ad8b') +        self.DL.params['writesubtitles'] = True +        self.DL.params['subtitlesformat'] = 'sbv' +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b')      def test_youtube_subtitles_vtt_format(self): -        DL = FakeYDL() -        DL.params['writesubtitles'] = True -        DL.params['subtitlesformat'] = 'vtt' -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') -        sub = info_dict[0]['subtitles'][0] -        self.assertEqual(md5(sub[2]), '356cdc577fde0c6783b9b822e7206ff7') +        self.DL.params['writesubtitles'] = True +        self.DL.params['subtitlesformat'] = 'vtt' +        subtitles = self.getSubtitles() +        self.assertEqual(md5(subtitles['en']), '356cdc577fde0c6783b9b822e7206ff7')      def test_youtube_list_subtitles(self): -        DL = FakeYDL() -        DL.params['listsubtitles'] = True -        IE = YoutubeIE(DL) -        info_dict = IE.extract('QRS8MkLhQmM') +        self.DL.params['listsubtitles'] = True +        info_dict = self.getInfoDict()          self.assertEqual(info_dict, None)      def test_youtube_automatic_captions(self): -        DL = FakeYDL() -        DL.params['writeautomaticsub'] = True -        DL.params['subtitleslang'] = 'it' -        IE = YoutubeIE(DL) -        info_dict = IE.extract('8YoUxe5ncPo') -        sub = info_dict[0]['subtitles'][0] -        self.assertTrue(sub[2] is not None) +        self.url = '8YoUxe5ncPo' +        self.DL.params['writeautomaticsub'] = True +        self.DL.params['subtitleslang'] = 'it' +        subtitles = self.getSubtitles() +        self.assertTrue(subtitles['it'] is not None) +    def test_youtube_nosubtitles(self): +        self.url = 'sAjKT8FhjI8' +        self.DL.params['allsubtitles'] = True +        subtitles = self.getSubtitles() +        self.assertEqual(len(subtitles), 0) +  if __name__ == '__main__':      unittest.main() diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index d1618da79..fa7bb1387 100644 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -221,19 +221,16 @@ class YoutubeDL(object):      def report_writesubtitles(self, sub_filename):          """ Report that the subtitles file is being written """ -        self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename) +        self.to_screen(u'[info] Writing subtitle: ' + sub_filename) + +    def report_existingsubtitles(self, sub_filename): +        """ Report that the subtitles file has been already written """ +        self.to_screen(u'[info] Skipping existing subtitle: ' + sub_filename)      def report_writeinfojson(self, infofn):          """ Report that the metadata file has been written """          self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn) -    def report_file_already_downloaded(self, file_name): -        """Report file has already been fully downloaded.""" -        try: -            self.to_screen(u'[download] %s has already been downloaded' % file_name) -        except (UnicodeEncodeError) as err: -            self.to_screen(u'[download] The file has already been downloaded') -      def increment_downloads(self):          """Increment the ordinal that assigns a number to each file."""          self._num_downloads += 1 @@ -483,41 +480,32 @@ class YoutubeDL(object):                  self.report_error(u'Cannot write description file ' + descfn)                  return -        if (self.params.get('writesubtitles', False) or self.params.get('writeautomaticsub')) and 'subtitles' in info_dict and info_dict['subtitles']: +        subtitles_are_requested = any([self.params.get('writesubtitles', False), +                                       self.params.get('writeautomaticsub'), +                                       self.params.get('allsubtitles', False)]) + +        if  subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:              # subtitles download errors are already managed as troubles in relevant IE              # that way it will silently go on when used with unsupporting IE -            subtitle = info_dict['subtitles'][0] -            (sub_error, sub_lang, sub) = subtitle +            subtitles = info_dict['subtitles']              sub_format = self.params.get('subtitlesformat') -            if sub_error: -                self.report_warning("Some error while getting the subtitles") -            else: + +            for sub_lang in subtitles.keys(): +                sub = subtitles[sub_lang] +                if sub is None: +                    continue                  try:                      sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format +                    if os.path.isfile(encodeFilename(sub_filename)): +                        self.report_existingsubtitles(sub_filename) +                        continue                      self.report_writesubtitles(sub_filename)                      with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: -                        subfile.write(sub) +                            subfile.write(sub)                  except (OSError, IOError):                      self.report_error(u'Cannot write subtitles file ' + descfn)                      return -        if self.params.get('allsubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']: -            subtitles = info_dict['subtitles'] -            sub_format = self.params.get('subtitlesformat') -            for subtitle in subtitles: -                (sub_error, sub_lang, sub) = subtitle -                if sub_error: -                    self.report_warning("Some error while getting the subtitles") -                else: -                    try: -                        sub_filename = filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format -                        self.report_writesubtitles(sub_filename) -                        with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile: -                                subfile.write(sub) -                    except (OSError, IOError): -                        self.report_error(u'Cannot write subtitles file ' + descfn) -                        return -          if self.params.get('writeinfojson', False):              infofn = filename + u'.info.json'              self.report_writeinfojson(infofn) diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index f4890f1a6..c21bf6d4a 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -190,13 +190,10 @@ def parseOpts(overrideArguments=None):      subtitles.add_option('--write-sub', '--write-srt',              action='store_true', dest='writesubtitles', -            help='write subtitle file (currently youtube only)', default=False) +            help='write subtitle file', default=False)      subtitles.add_option('--write-auto-sub', '--write-automatic-sub',              action='store_true', dest='writeautomaticsub', -            help='write automatic subtitle file (currently youtube only)', default=False) -    subtitles.add_option('--only-sub', -            action='store_true', dest='skip_download', -            help='[deprecated] alias of --skip-download', default=False) +            help='write automatic subtitle file (youtube only)', default=False)      subtitles.add_option('--all-subs',              action='store_true', dest='allsubtitles',              help='downloads all the available subtitles of the video', default=False) diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 8009c2d85..52c4483c9 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -47,7 +47,8 @@ class InfoExtractor(object):      uploader_id:    Nickname or id of the video uploader.      location:       Physical location of the video.      player_url:     SWF Player URL (used for rtmpdump). -    subtitles:      The subtitle file contents. +    subtitles:      The subtitle file contents as a dictionary in the format +                    {language: subtitles}.      view_count:     How many users have watched the video on the platform.      urlhandle:      [internal] The urlHandle to be used to download the file,                      like returned by urllib.request.urlopen diff --git a/youtube_dl/extractor/dailymotion.py b/youtube_dl/extractor/dailymotion.py index fa8c630d0..f54ecc569 100644 --- a/youtube_dl/extractor/dailymotion.py +++ b/youtube_dl/extractor/dailymotion.py @@ -1,17 +1,39 @@  import re  import json -import itertools +import socket  from .common import InfoExtractor +from .subtitles import NoAutoSubtitlesIE +  from ..utils import ( +    compat_http_client, +    compat_urllib_error,      compat_urllib_request, +    compat_str,      get_element_by_attribute,      get_element_by_id,      ExtractorError,  ) -class DailymotionIE(InfoExtractor): + +class DailyMotionSubtitlesIE(NoAutoSubtitlesIE): + +    def _get_available_subtitles(self, video_id): +        request = compat_urllib_request.Request('https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id) +        try: +            sub_list = compat_urllib_request.urlopen(request).read().decode('utf-8') +        except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: +            self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) +            return {} +        info = json.loads(sub_list) +        if (info['total'] > 0): +            sub_lang_list = dict((l['language'], l['url']) for l in info['list']) +            return sub_lang_list +        self._downloader.report_warning(u'video doesn\'t have subtitles') +        return {} + +class DailymotionIE(DailyMotionSubtitlesIE):      """Information Extractor for Dailymotion"""      _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)' @@ -21,7 +43,7 @@ class DailymotionIE(InfoExtractor):          u'file': u'x33vw9.mp4',          u'md5': u'392c4b85a60a90dc4792da41ce3144eb',          u'info_dict': { -            u"uploader": u"Alex and Van .",  +            u"uploader": u"Alex and Van .",              u"title": u"Tutoriel de Youtubeur\"DL DES VIDEO DE YOUTUBE\""          }      } @@ -63,14 +85,27 @@ class DailymotionIE(InfoExtractor):          for key in ['stream_h264_hd1080_url','stream_h264_hd_url',                      'stream_h264_hq_url','stream_h264_url',                      'stream_h264_ld_url']: -            if info.get(key):#key in info and info[key]: +            if info.get(key):  # key in info and info[key]:                  max_quality = key -                self.to_screen(u'Using %s' % key) +                self.to_screen(u'%s: Using %s' % (video_id, key))                  break          else:              raise ExtractorError(u'Unable to extract video URL')          video_url = info[max_quality] +        # subtitles +        video_subtitles = None +        video_webpage = None + +        if self._downloader.params.get('writesubtitles', False) or self._downloader.params.get('allsubtitles', False): +            video_subtitles = self._extract_subtitles(video_id) +        elif self._downloader.params.get('writeautomaticsub', False): +            video_subtitles = self._request_automatic_caption(video_id, video_webpage) + +        if self._downloader.params.get('listsubtitles', False): +            self._list_available_subtitles(video_id) +            return +          return [{              'id':       video_id,              'url':      video_url, @@ -78,6 +113,7 @@ class DailymotionIE(InfoExtractor):              'upload_date':  video_upload_date,              'title':    self._og_search_title(webpage),              'ext':      video_extension, +            'subtitles':    video_subtitles,              'thumbnail': info['thumbnail_url']          }] diff --git a/youtube_dl/extractor/subtitles.py b/youtube_dl/extractor/subtitles.py new file mode 100644 index 000000000..caacea5fe --- /dev/null +++ b/youtube_dl/extractor/subtitles.py @@ -0,0 +1,75 @@ +import socket + +from .common import InfoExtractor + +from ..utils import ( +    compat_http_client, +    compat_urllib_error, +    compat_urllib_request, +    compat_str, +) + + +class SubtitlesIE(InfoExtractor): + +    def _list_available_subtitles(self, video_id): +        """ outputs the available subtitles for the video """ +        sub_lang_list = self._get_available_subtitles(video_id) +        sub_lang = ",".join(list(sub_lang_list.keys())) +        self.to_screen(u'%s: Available subtitles for video: %s' % +                       (video_id, sub_lang)) + +    def _extract_subtitles(self, video_id): +        """ returns {sub_lang: sub} or {} if subtitles not found """ +        sub_lang_list = self._get_available_subtitles(video_id) +        if not sub_lang_list:  # error, it didn't get the available subtitles +            return {} + +        if self._downloader.params.get('writesubtitles', False): +            if self._downloader.params.get('subtitleslang', False): +                sub_lang = self._downloader.params.get('subtitleslang') +            elif 'en' in sub_lang_list: +                sub_lang = 'en' +            else: +                sub_lang = list(sub_lang_list.keys())[0] +            if not sub_lang in sub_lang_list: +                self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) +                return {} +            sub_lang_list = {sub_lang: sub_lang_list[sub_lang]} + +        subtitles = {} +        for sub_lang, url in sub_lang_list.iteritems(): +            subtitle = self._request_subtitle_url(sub_lang, url) +            if subtitle: +                subtitles[sub_lang] = subtitle +        return subtitles + +    def _request_subtitle_url(self, sub_lang, url): +        """ makes the http request for the subtitle """ +        try: +            sub = compat_urllib_request.urlopen(url).read().decode('utf-8') +        except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: +            self._downloader.report_warning(u'unable to download video subtitles for %s: %s' % (sub_lang, compat_str(err))) +            return +        if not sub: +            self._downloader.report_warning(u'Did not fetch video subtitles') +            return +        return sub + +    def _get_available_subtitles(self, video_id): +        """ returns {sub_lang: url} or {} if not available """ +        """ Must be redefined by the subclasses """ +        pass + +    def _request_automatic_caption(self, video_id, webpage): +        """ returns {sub_lang: sub} or {} if not available """ +        """ Must be redefined by the subclasses """ +        pass + + +class NoAutoSubtitlesIE(SubtitlesIE): +    """ A subtitle class for the servers that don't support auto-captions""" + +    def _request_automatic_caption(self, video_id, webpage): +        self._downloader.report_warning(u'Automatic Captions not supported by this server') +        return {} diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index e402ef17f..571c73889 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -7,6 +7,7 @@ import socket  import itertools  from .common import InfoExtractor, SearchInfoExtractor +from .subtitles import SubtitlesIE  from ..utils import (      compat_http_client,      compat_parse_qs, @@ -23,114 +24,67 @@ from ..utils import (      orderedSet,  ) -class YoutubeBaseInfoExtractor(InfoExtractor): -    """Provide base functions for Youtube extractors""" -    _LOGIN_URL = 'https://accounts.google.com/ServiceLogin' -    _LANG_URL = r'https://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1' -    _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en' -    _NETRC_MACHINE = 'youtube' -    # If True it will raise an error if no login info is provided -    _LOGIN_REQUIRED = False - -    def report_lang(self): -        """Report attempt to set language.""" -        self.to_screen(u'Setting language') - -    def _set_language(self): -        request = compat_urllib_request.Request(self._LANG_URL) -        try: -            self.report_lang() -            compat_urllib_request.urlopen(request).read() -        except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: -            self._downloader.report_warning(u'unable to set language: %s' % compat_str(err)) -            return False -        return True - -    def _login(self): -        (username, password) = self._get_login_info() -        # No authentication to be performed -        if username is None: -            if self._LOGIN_REQUIRED: -                raise ExtractorError(u'No login info available, needed for using %s.' % self.IE_NAME, expected=True) -            return False - -        request = compat_urllib_request.Request(self._LOGIN_URL) -        try: -            login_page = compat_urllib_request.urlopen(request).read().decode('utf-8') -        except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: -            self._downloader.report_warning(u'unable to fetch login page: %s' % compat_str(err)) -            return False - -        galx = None -        dsh = None -        match = re.search(re.compile(r'<input.+?name="GALX".+?value="(.+?)"', re.DOTALL), login_page) -        if match: -          galx = match.group(1) -        match = re.search(re.compile(r'<input.+?name="dsh".+?value="(.+?)"', re.DOTALL), login_page) -        if match: -          dsh = match.group(1) - -        # Log in -        login_form_strs = { -                u'continue': u'https://www.youtube.com/signin?action_handle_signin=true&feature=sign_in_button&hl=en_US&nomobiletemp=1', -                u'Email': username, -                u'GALX': galx, -                u'Passwd': password, -                u'PersistentCookie': u'yes', -                u'_utf8': u'霱', -                u'bgresponse': u'js_disabled', -                u'checkConnection': u'', -                u'checkedDomains': u'youtube', -                u'dnConn': u'', -                u'dsh': dsh, -                u'pstMsg': u'0', -                u'rmShown': u'1', -                u'secTok': u'', -                u'signIn': u'Sign in', -                u'timeStmp': u'', -                u'service': u'youtube', -                u'uilel': u'3', -                u'hl': u'en_US', -        } -        # Convert to UTF-8 *before* urlencode because Python 2.x's urlencode -        # chokes on unicode -        login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in login_form_strs.items()) -        login_data = compat_urllib_parse.urlencode(login_form).encode('ascii') -        request = compat_urllib_request.Request(self._LOGIN_URL, login_data) + +class YoutubeSubtitlesIE(SubtitlesIE): + +    def _get_available_subtitles(self, video_id): +        request = compat_urllib_request.Request('http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id)          try: -            self.report_login() -            login_results = compat_urllib_request.urlopen(request).read().decode('utf-8') -            if re.search(r'(?i)<form[^>]* id="gaia_loginform"', login_results) is not None: -                self._downloader.report_warning(u'unable to log in: bad username or password') -                return False +            sub_list = compat_urllib_request.urlopen(request).read().decode('utf-8')          except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: -            self._downloader.report_warning(u'unable to log in: %s' % compat_str(err)) -            return False -        return True - -    def _confirm_age(self): -        age_form = { -                'next_url':     '/', -                'action_confirm':   'Confirm', -                } -        request = compat_urllib_request.Request(self._AGE_URL, compat_urllib_parse.urlencode(age_form)) +            self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) +            return {} +        lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', sub_list) + +        sub_lang_list = {} +        for l in lang_list: +            lang = l[1] +            params = compat_urllib_parse.urlencode({ +                'lang': lang, +                'v': video_id, +                'fmt': self._downloader.params.get('subtitlesformat'), +            }) +            url = u'http://www.youtube.com/api/timedtext?' + params +            sub_lang_list[lang] = url +        if not sub_lang_list: +            self._downloader.report_warning(u'video doesn\'t have subtitles') +            return {} +        return sub_lang_list + +    def _request_automatic_caption(self, video_id, webpage): +        """We need the webpage for getting the captions url, pass it as an +           argument to speed up the process.""" +        sub_lang = self._downloader.params.get('subtitleslang') or 'en' +        sub_format = self._downloader.params.get('subtitlesformat') +        self.to_screen(u'%s: Looking for automatic captions' % video_id) +        mobj = re.search(r';ytplayer.config = ({.*?});', webpage) +        err_msg = u'Couldn\'t find automatic captions for "%s"' % sub_lang +        if mobj is None: +            self._downloader.report_warning(err_msg) +            return {} +        player_config = json.loads(mobj.group(1))          try: -            self.report_age_confirmation() -            compat_urllib_request.urlopen(request).read().decode('utf-8') -        except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: -            raise ExtractorError(u'Unable to confirm age: %s' % compat_str(err)) -        return True +            args = player_config[u'args'] +            caption_url = args[u'ttsurl'] +            timestamp = args[u'timestamp'] +            params = compat_urllib_parse.urlencode({ +                'lang': 'en', +                'tlang': sub_lang, +                'fmt': sub_format, +                'ts': timestamp, +                'kind': 'asr', +            }) +            subtitles_url = caption_url + '&' + params +            sub = self._download_webpage(subtitles_url, video_id, u'Downloading automatic captions') +            return {sub_lang: sub} +        # An extractor error can be raise by the download process if there are +        # no automatic captions but there are subtitles +        except (KeyError, ExtractorError): +            self._downloader.report_warning(err_msg) +            return {} -    def _real_initialize(self): -        if self._downloader is None: -            return -        if not self._set_language(): -            return -        if not self._login(): -            return -        self._confirm_age() -class YoutubeIE(YoutubeBaseInfoExtractor): +class YoutubeIE(YoutubeSubtitlesIE):      IE_DESC = u'YouTube.com'      _VALID_URL = r"""^                       ( @@ -141,7 +95,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):                           (?:                                                  # 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|movie)(?:_popup)?(?:\.php)?)?    # preceding watch(_popup|.php) or nothing (like /?v=xxxx) +                                 (?:watch|movie(?:_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= @@ -384,19 +338,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):          """Report attempt to download video info webpage."""          self.to_screen(u'%s: Downloading video info webpage' % video_id) -    def report_video_subtitles_download(self, video_id): -        """Report attempt to download video info webpage.""" -        self.to_screen(u'%s: Checking available subtitles' % video_id) - -    def report_video_subtitles_request(self, video_id, sub_lang, format): -        """Report attempt to download video info webpage.""" -        self.to_screen(u'%s: Downloading video subtitles for %s.%s' % (video_id, sub_lang, format)) - -    def report_video_subtitles_available(self, video_id, sub_lang_list): -        """Report available subtitles.""" -        sub_lang = ",".join(list(sub_lang_list.keys())) -        self.to_screen(u'%s: Available subtitles for video: %s' % (video_id, sub_lang)) -      def report_information_extraction(self, video_id):          """Report attempt to extract video information."""          self.to_screen(u'%s: Extracting video information' % video_id) @@ -736,25 +677,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):          # subtitles          video_subtitles = None -        if self._downloader.params.get('writesubtitles', False): -            video_subtitles = self._extract_subtitle(video_id) -            if video_subtitles: -                (sub_error, sub_lang, sub) = video_subtitles[0] -                if sub_error: -                    self._downloader.report_warning(sub_error) -         -        if self._downloader.params.get('writeautomaticsub', False): +        if self._downloader.params.get('writesubtitles', False) or self._downloader.params.get('allsubtitles', False): +            video_subtitles = self._extract_subtitles(video_id) +        elif self._downloader.params.get('writeautomaticsub', False):              video_subtitles = self._request_automatic_caption(video_id, video_webpage) -            (sub_error, sub_lang, sub) = video_subtitles[0] -            if sub_error: -                self._downloader.report_warning(sub_error) - -        if self._downloader.params.get('allsubtitles', False): -            video_subtitles = self._extract_all_subtitles(video_id) -            for video_subtitle in video_subtitles: -                (sub_error, sub_lang, sub) = video_subtitle -                if sub_error: -                    self._downloader.report_warning(sub_error)          if self._downloader.params.get('listsubtitles', False):              self._list_available_subtitles(video_id) | 
