diff options
Diffstat (limited to 'youtube_dl/extractor')
| -rw-r--r-- | youtube_dl/extractor/__init__.py | 9 | ||||
| -rw-r--r-- | youtube_dl/extractor/arte.py | 4 | ||||
| -rw-r--r-- | youtube_dl/extractor/auengine.py | 31 | ||||
| -rw-r--r-- | youtube_dl/extractor/bambuser.py | 3 | ||||
| -rw-r--r-- | youtube_dl/extractor/canalplus.py | 3 | ||||
| -rw-r--r-- | youtube_dl/extractor/common.py | 48 | ||||
| -rw-r--r-- | youtube_dl/extractor/d8.py | 22 | ||||
| -rw-r--r-- | youtube_dl/extractor/dailymotion.py | 2 | ||||
| -rw-r--r-- | youtube_dl/extractor/gamekings.py | 6 | ||||
| -rw-r--r-- | youtube_dl/extractor/generic.py | 31 | ||||
| -rw-r--r-- | youtube_dl/extractor/livestream.py | 44 | ||||
| -rw-r--r-- | youtube_dl/extractor/mtv.py | 2 | ||||
| -rw-r--r-- | youtube_dl/extractor/redtube.py | 4 | ||||
| -rw-r--r-- | youtube_dl/extractor/rtlnow.py | 12 | ||||
| -rw-r--r-- | youtube_dl/extractor/soundcloud.py | 2 | ||||
| -rw-r--r-- | youtube_dl/extractor/southparkstudios.py | 25 | ||||
| -rw-r--r-- | youtube_dl/extractor/spiegel.py | 50 | ||||
| -rw-r--r-- | youtube_dl/extractor/ted.py | 38 | ||||
| -rw-r--r-- | youtube_dl/extractor/toutv.py | 75 | ||||
| -rw-r--r-- | youtube_dl/extractor/tvp.py | 5 | ||||
| -rw-r--r-- | youtube_dl/extractor/vevo.py | 7 | ||||
| -rw-r--r-- | youtube_dl/extractor/youtube.py | 67 | 
22 files changed, 366 insertions, 124 deletions
diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 0594a3666..b0df1cef7 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -25,6 +25,7 @@ from .comedycentral import ComedyCentralIE  from .condenast import CondeNastIE  from .criterion import CriterionIE  from .cspan import CSpanIE +from .d8 import D8IE  from .dailymotion import (      DailymotionIE,      DailymotionPlaylistIE, @@ -80,7 +81,7 @@ from .keezmovies import KeezMoviesIE  from .kickstarter import KickStarterIE  from .keek import KeekIE  from .liveleak import LiveLeakIE -from .livestream import LivestreamIE +from .livestream import LivestreamIE, LivestreamOriginalIE  from .metacafe import MetacafeIE  from .metacritic import MetacriticIE  from .mit import TechTVMITIE, MITIE @@ -116,7 +117,10 @@ from .slashdot import SlashdotIE  from .slideshare import SlideshareIE  from .sohu import SohuIE  from .soundcloud import SoundcloudIE, SoundcloudSetIE, SoundcloudUserIE -from .southparkstudios import SouthParkStudiosIE +from .southparkstudios import ( +    SouthParkStudiosIE, +    SouthparkDeIE, +)  from .space import SpaceIE  from .spankwire import SpankwireIE  from .spiegel import SpiegelIE @@ -129,6 +133,7 @@ from .techtalks import TechTalksIE  from .ted import TEDIE  from .tf1 import TF1IE  from .thisav import ThisAVIE +from .toutv import TouTvIE  from .traileraddict import TrailerAddictIE  from .trilulilu import TriluliluIE  from .tube8 import Tube8IE diff --git a/youtube_dl/extractor/arte.py b/youtube_dl/extractor/arte.py index b35a679e3..44d0b5d70 100644 --- a/youtube_dl/extractor/arte.py +++ b/youtube_dl/extractor/arte.py @@ -69,7 +69,7 @@ class ArteTvIE(InfoExtractor):              lang = mobj.group('lang')              return self._extract_liveweb(url, name, lang) -        if re.search(self._LIVE_URL, video_id) is not None: +        if re.search(self._LIVE_URL, url) is not None:              raise ExtractorError(u'Arte live streams are not yet supported, sorry')              # self.extractLiveStream(url)              # return @@ -115,7 +115,7 @@ class ArteTvIE(InfoExtractor):          event_doc = config_doc.find('event')          url_node = event_doc.find('video').find('urlHd')          if url_node is None: -            url_node = video_doc.find('urlSd') +            url_node = event_doc.find('urlSd')          return {'id': video_id,                  'title': event_doc.find('name%s' % lang.capitalize()).text, diff --git a/youtube_dl/extractor/auengine.py b/youtube_dl/extractor/auengine.py index 0febbff4f..95c038003 100644 --- a/youtube_dl/extractor/auengine.py +++ b/youtube_dl/extractor/auengine.py @@ -1,10 +1,10 @@ -import os.path  import re  from .common import InfoExtractor  from ..utils import (      compat_urllib_parse, -    compat_urllib_parse_urlparse, +    determine_ext, +    ExtractorError,  )  class AUEngineIE(InfoExtractor): @@ -25,22 +25,25 @@ class AUEngineIE(InfoExtractor):          title = self._html_search_regex(r'<title>(?P<title>.+?)</title>',                  webpage, u'title')          title = title.strip() -        links = re.findall(r'[^A-Za-z0-9]?(?:file|url):\s*["\'](http[^\'"&]*)', webpage) -        links = [compat_urllib_parse.unquote(l) for l in links] +        links = re.findall(r'\s(?:file|url):\s*["\']([^\'"]+)["\']', webpage) +        links = map(compat_urllib_parse.unquote, links) + +        thumbnail = None +        video_url = None          for link in links: -            root, pathext = os.path.splitext(compat_urllib_parse_urlparse(link).path) -            if pathext == '.png': +            if link.endswith('.png'):                  thumbnail = link -            elif pathext == '.mp4': -                url = link -                ext = pathext +            elif '/videos/' in link: +                video_url = link +        if not video_url: +            raise ExtractorError(u'Could not find video URL') +        ext = u'.' + determine_ext(video_url)          if ext == title[-len(ext):]:              title = title[:-len(ext)] -        ext = ext[1:] -        return [{ + +        return {              'id':        video_id, -            'url':       url, -            'ext':       ext, +            'url':       video_url,              'title':     title,              'thumbnail': thumbnail, -        }] +        } diff --git a/youtube_dl/extractor/bambuser.py b/youtube_dl/extractor/bambuser.py index f3b36f473..967568c4a 100644 --- a/youtube_dl/extractor/bambuser.py +++ b/youtube_dl/extractor/bambuser.py @@ -15,7 +15,8 @@ class BambuserIE(InfoExtractor):      _TEST = {          u'url': u'http://bambuser.com/v/4050584', -        u'md5': u'fba8f7693e48fd4e8641b3fd5539a641', +        # MD5 seems to be flaky, see https://travis-ci.org/rg3/youtube-dl/jobs/14051016#L388 +        #u'md5': u'fba8f7693e48fd4e8641b3fd5539a641',          u'info_dict': {              u'id': u'4050584',              u'ext': u'flv', diff --git a/youtube_dl/extractor/canalplus.py b/youtube_dl/extractor/canalplus.py index 1db9b24cf..bfa2a8b40 100644 --- a/youtube_dl/extractor/canalplus.py +++ b/youtube_dl/extractor/canalplus.py @@ -5,6 +5,7 @@ import xml.etree.ElementTree  from .common import InfoExtractor  from ..utils import unified_strdate +  class CanalplusIE(InfoExtractor):      _VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>\d+))'      _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s' @@ -25,7 +26,7 @@ class CanalplusIE(InfoExtractor):      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url) -        video_id = mobj.group('id') +        video_id = mobj.groupdict().get('id')          if video_id is None:              webpage = self._download_webpage(url, mobj.group('path'))              video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id') diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 9c20d30b4..eb3435c77 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -71,6 +71,10 @@ class InfoExtractor(object):                                  ("3D" or "DASH video")                      * width     Width of the video, if known                      * height    Height of the video, if known +                    * abr       Average audio bitrate in KBit/s +                    * acodec    Name of the audio codec in use +                    * vbr       Average video bitrate in KBit/s +                    * vcodec    Name of the video codec in use      webpage_url:    The url to the video webpage, if given to youtube-dl it                      should allow to get the same result again. (It will be set                      by YoutubeDL if it's missing) @@ -315,13 +319,19 @@ class InfoExtractor(object):      # Helper functions for extracting OpenGraph info      @staticmethod -    def _og_regex(prop): -        return r'<meta.+?property=[\'"]og:%s[\'"].+?content=(?:"(.+?)"|\'(.+?)\')' % re.escape(prop) +    def _og_regexes(prop): +        content_re = r'content=(?:"([^>]+?)"|\'(.+?)\')' +        property_re = r'property=[\'"]og:%s[\'"]' % re.escape(prop) +        template = r'<meta[^>]+?%s[^>]+?%s' +        return [ +            template % (property_re, content_re), +            template % (content_re, property_re), +        ]      def _og_search_property(self, prop, html, name=None, **kargs):          if name is None:              name = 'OpenGraph %s' % prop -        escaped = self._search_regex(self._og_regex(prop), html, name, flags=re.DOTALL, **kargs) +        escaped = self._search_regex(self._og_regexes(prop), html, name, flags=re.DOTALL, **kargs)          if escaped is None:              return None          return unescapeHTML(escaped) @@ -336,10 +346,21 @@ class InfoExtractor(object):          return self._og_search_property('title', html, **kargs)      def _og_search_video_url(self, html, name='video url', secure=True, **kargs): -        regexes = [self._og_regex('video')] -        if secure: regexes.insert(0, self._og_regex('video:secure_url')) +        regexes = self._og_regexes('video') +        if secure: regexes = self._og_regexes('video:secure_url') + regexes          return self._html_search_regex(regexes, html, name, **kargs) +    def _html_search_meta(self, name, html, display_name=None): +        if display_name is None: +            display_name = name +        return self._html_search_regex( +            r'''(?ix)<meta(?=[^>]+(?:name|property)=["\']%s["\']) +                    [^>]+content=["\']([^"\']+)["\']''' % re.escape(name), +            html, display_name, fatal=False) + +    def _dc_search_uploader(self, html): +        return self._html_search_meta('dc.creator', html, 'uploader') +      def _rta_search(self, html):          # See http://www.rtalabel.org/index.php?content=howtofaq#single          if re.search(r'(?ix)<meta\s+name="rating"\s+' @@ -348,6 +369,23 @@ class InfoExtractor(object):              return 18          return 0 +    def _media_rating_search(self, html): +        # See http://www.tjg-designs.com/WP/metadata-code-examples-adding-metadata-to-your-web-pages/ +        rating = self._html_search_meta('rating', html) + +        if not rating: +            return None + +        RATING_TABLE = { +            'safe for kids': 0, +            'general': 8, +            '14 years': 14, +            'mature': 17, +            'restricted': 19, +        } +        return RATING_TABLE.get(rating.lower(), None) + +  class SearchInfoExtractor(InfoExtractor):      """ diff --git a/youtube_dl/extractor/d8.py b/youtube_dl/extractor/d8.py new file mode 100644 index 000000000..a56842b16 --- /dev/null +++ b/youtube_dl/extractor/d8.py @@ -0,0 +1,22 @@ +# encoding: utf-8 +from .canalplus import CanalplusIE + + +class D8IE(CanalplusIE): +    _VALID_URL = r'https?://www\.d8\.tv/.*?/(?P<path>.*)' +    _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/d8/%s' +    IE_NAME = u'd8.tv' + +    _TEST = { +        u'url': u'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html', +        u'file': u'966289.flv', +        u'info_dict': { +            u'title': u'Campagne intime - Documentaire exceptionnel', +            u'description': u'md5:d2643b799fb190846ae09c61e59a859f', +            u'upload_date': u'20131108', +        }, +        u'params': { +            # rtmp +            u'skip_download': True, +        }, +    } diff --git a/youtube_dl/extractor/dailymotion.py b/youtube_dl/extractor/dailymotion.py index e87690f9d..71f5e03ee 100644 --- a/youtube_dl/extractor/dailymotion.py +++ b/youtube_dl/extractor/dailymotion.py @@ -186,7 +186,7 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):              webpage = self._download_webpage(request,                                               id, u'Downloading page %s' % pagenum) -            playlist_el = get_element_by_attribute(u'class', u'video_list', webpage) +            playlist_el = get_element_by_attribute(u'class', u'row video_list', webpage)              video_ids.extend(re.findall(r'data-id="(.+?)"', playlist_el))              if re.search(self._MORE_PAGES_INDICATOR, webpage, re.DOTALL) is None: diff --git a/youtube_dl/extractor/gamekings.py b/youtube_dl/extractor/gamekings.py index 6aa657ef4..c91669b0e 100644 --- a/youtube_dl/extractor/gamekings.py +++ b/youtube_dl/extractor/gamekings.py @@ -1,9 +1,6 @@  import re  from .common import InfoExtractor -from ..utils import ( -    determine_ext, -)  class GamekingsIE(InfoExtractor): @@ -11,7 +8,8 @@ class GamekingsIE(InfoExtractor):      _TEST = {          u"url": u"http://www.gamekings.tv/videos/phoenix-wright-ace-attorney-dual-destinies-review/",          u'file': u'20130811.mp4', -        u'md5': u'2f32b1f7b80fdc5cb616efb4f387f8a3', +        # MD5 is flaky, seems to change regularly +        #u'md5': u'2f32b1f7b80fdc5cb616efb4f387f8a3',          u'info_dict': {              u"title": u"Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review",              u"description": u"Melle en Steven hebben voor de review een week in de rechtbank doorbracht met Phoenix Wright: Ace Attorney - Dual Destinies.", diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index c7552fddb..e1d6a2a01 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -162,6 +162,16 @@ class GenericIE(InfoExtractor):              raise ExtractorError(u'Failed to download URL: %s' % url)          self.report_extraction(video_id) + +        # it's tempting to parse this further, but you would +        # have to take into account all the variations like +        #   Video Title - Site Name +        #   Site Name | Video Title +        #   Video Title - Tagline | Site Name +        # and so on and so forth; it's just not practical +        video_title = self._html_search_regex(r'<title>(.*)</title>', +            webpage, u'video title', default=u'video', flags=re.DOTALL) +          # Look for BrightCove:          bc_url = BrightcoveIE._extract_brightcove_url(webpage)          if bc_url is not None: @@ -177,11 +187,13 @@ class GenericIE(InfoExtractor):              return self.url_result(surl, 'Vimeo')          # Look for embedded YouTube player -        mobj = re.search( -            r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?youtube.com/embed/.+?)\1', webpage) -        if mobj: -            surl = unescapeHTML(mobj.group(u'url')) -            return self.url_result(surl, 'Youtube') +        matches = re.findall( +            r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?youtube.com/embed/.+?)\1', webpage) +        if matches: +            urlrs = [self.url_result(unescapeHTML(tuppl[1]), 'Youtube') +                     for tuppl in matches] +            return self.playlist_result( +                urlrs, playlist_id=video_id, playlist_title=video_title)          # Look for Bandcamp pages with custom domain          mobj = re.search(r'<meta property="og:url"[^>]*?content="(.*?bandcamp\.com.*?)"', webpage) @@ -226,15 +238,6 @@ class GenericIE(InfoExtractor):          video_extension = os.path.splitext(video_id)[1][1:]          video_id = os.path.splitext(video_id)[0] -        # it's tempting to parse this further, but you would -        # have to take into account all the variations like -        #   Video Title - Site Name -        #   Site Name | Video Title -        #   Video Title - Tagline | Site Name -        # and so on and so forth; it's just not practical -        video_title = self._html_search_regex(r'<title>(.*)</title>', -            webpage, u'video title', default=u'video', flags=re.DOTALL) -          # video uploader is domain name          video_uploader = self._search_regex(r'(?:https?://)?([^/]*)/.*',              url, u'video uploader') diff --git a/youtube_dl/extractor/livestream.py b/youtube_dl/extractor/livestream.py index 4531fd6ab..1a3e0ae6b 100644 --- a/youtube_dl/extractor/livestream.py +++ b/youtube_dl/extractor/livestream.py @@ -1,16 +1,19 @@  import re  import json +import xml.etree.ElementTree  from .common import InfoExtractor  from ..utils import (      compat_urllib_parse_urlparse,      compat_urlparse,      get_meta_content, +    xpath_with_ns,      ExtractorError,  )  class LivestreamIE(InfoExtractor): +    IE_NAME = u'livestream'      _VALID_URL = r'http://new.livestream.com/.*?/(?P<event_name>.*?)(/videos/(?P<id>\d+))?/?$'      _TEST = {          u'url': u'http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370', @@ -54,3 +57,44 @@ class LivestreamIE(InfoExtractor):              info = json.loads(self._download_webpage(api_url, video_id,                                                       u'Downloading video info'))              return self._extract_video_info(info) + + +# The original version of Livestream uses a different system +class LivestreamOriginalIE(InfoExtractor): +    IE_NAME = u'livestream:original' +    _VALID_URL = r'https?://www\.livestream\.com/(?P<user>[^/]+)/video\?.*?clipId=(?P<id>.*?)(&|$)' +    _TEST = { +        u'url': u'http://www.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb', +        u'info_dict': { +            u'id': u'pla_8aa4a3f1-ba15-46a4-893b-902210e138fb', +            u'ext': u'flv', +            u'title': u'Spark 1 (BitCoin) with Cameron Winklevoss & Tyler Winklevoss of Winklevoss Capital', +        }, +        u'params': { +            # rtmp +            u'skip_download': True, +        }, +    } + +    def _real_extract(self, url): +        mobj = re.match(self._VALID_URL, url) +        video_id = mobj.group('id') +        user = mobj.group('user') +        api_url = 'http://x{0}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={1}'.format(user, video_id) + +        api_response = self._download_webpage(api_url, video_id) +        info = xml.etree.ElementTree.fromstring(api_response.encode('utf-8')) +        item = info.find('channel').find('item') +        ns = {'media': 'http://search.yahoo.com/mrss'} +        thumbnail_url = item.find(xpath_with_ns('media:thumbnail', ns)).attrib['url'] +        # Remove the extension and number from the path (like 1.jpg) +        path = self._search_regex(r'(user-files/.+)_.*?\.jpg$', thumbnail_url, u'path') + +        return { +            'id': video_id, +            'title': item.find('title').text, +            'url': 'rtmp://extondemand.livestream.com/ondemand', +            'play_path': 'mp4:trans/dv15/mogulus-{0}.mp4'.format(path), +            'ext': 'flv', +            'thumbnail': thumbnail_url, +        } diff --git a/youtube_dl/extractor/mtv.py b/youtube_dl/extractor/mtv.py index 24a79ae13..3df7f9b85 100644 --- a/youtube_dl/extractor/mtv.py +++ b/youtube_dl/extractor/mtv.py @@ -48,7 +48,7 @@ class MTVIE(InfoExtractor):      def _transform_rtmp_url(rtmp_video_url):          m = re.match(r'^rtmpe?://.*?/(?P<finalid>gsp\..+?/.*)$', rtmp_video_url)          if not m: -            raise ExtractorError(u'Cannot transform RTMP url') +            return rtmp_video_url          base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/'          return base + m.group('finalid') diff --git a/youtube_dl/extractor/redtube.py b/youtube_dl/extractor/redtube.py index 994778e16..3bbda128e 100644 --- a/youtube_dl/extractor/redtube.py +++ b/youtube_dl/extractor/redtube.py @@ -8,7 +8,9 @@ class RedTubeIE(InfoExtractor):      _TEST = {          u'url': u'http://www.redtube.com/66418',          u'file': u'66418.mp4', -        u'md5': u'7b8c22b5e7098a3e1c09709df1126d2d', +        # md5 varies from time to time, as in +        # https://travis-ci.org/rg3/youtube-dl/jobs/14052463#L295 +        #u'md5': u'7b8c22b5e7098a3e1c09709df1126d2d',          u'info_dict': {              u"title": u"Sucked on a toilet",              u"age_limit": 18, diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py index 9ac7c3be8..2f238de35 100644 --- a/youtube_dl/extractor/rtlnow.py +++ b/youtube_dl/extractor/rtlnow.py @@ -63,18 +63,6 @@ class RTLnowIE(InfoExtractor):          },      },      { -        u'url': u'http://www.rtlnitronow.de/recht-ordnung/stadtpolizei-frankfurt-gerichtsvollzieher-leipzig.php?film_id=129679&player=1&season=1', -        u'file': u'129679.flv', -        u'info_dict': { -            u'upload_date': u'20131016',  -            u'title': u'Recht & Ordnung - Stadtpolizei Frankfurt/ Gerichtsvollzieher...', -            u'description': u'Stadtpolizei Frankfurt/ Gerichtsvollzieher Leipzig', -        }, -        u'params': { -            u'skip_download': True, -        }, -    }, -    {          u'url': u'http://www.n-tvnow.de/top-gear/episode-1-2013-01-01-00-00-00.php?film_id=124903&player=1&season=10',          u'file': u'124903.flv',          u'info_dict': { diff --git a/youtube_dl/extractor/soundcloud.py b/youtube_dl/extractor/soundcloud.py index 4717fbb77..83e1f055f 100644 --- a/youtube_dl/extractor/soundcloud.py +++ b/youtube_dl/extractor/soundcloud.py @@ -87,7 +87,7 @@ class SoundcloudIE(InfoExtractor):              'uploader': info['user']['username'],              'upload_date': unified_strdate(info['created_at']),              'title':    info['title'], -            'ext':      u'mp3', +            'ext':      info.get('original_format', u'mp3'),              'description': info['description'],              'thumbnail': thumbnail,          } diff --git a/youtube_dl/extractor/southparkstudios.py b/youtube_dl/extractor/southparkstudios.py index b1e96b679..a711531e6 100644 --- a/youtube_dl/extractor/southparkstudios.py +++ b/youtube_dl/extractor/southparkstudios.py @@ -5,21 +5,19 @@ from .mtv import MTVIE, _media_xml_tag  class SouthParkStudiosIE(MTVIE):      IE_NAME = u'southparkstudios.com' -    _VALID_URL = r'https?://www\.southparkstudios\.com/(clips|full-episodes)/(?P<id>.+?)(\?|#|$)' +    _VALID_URL = r'(https?://)?(www\.)?(?P<url>southparkstudios\.com/(clips|full-episodes)/(?P<id>.+?)(\?|#|$))'      _FEED_URL = 'http://www.southparkstudios.com/feeds/video-player/mrss' -    _TEST = { +    # Overwrite MTVIE properties we don't want +    _TESTS = [{          u'url': u'http://www.southparkstudios.com/clips/104437/bat-daded#tab=featured',          u'file': u'a7bff6c2-ed00-11e0-aca6-0026b9414f30.mp4',          u'info_dict': {              u'title': u'Bat Daded',              u'description': u'Randy disqualifies South Park by getting into a fight with Bat Dad.',          }, -    } - -    # Overwrite MTVIE properties we don't want -    _TESTS = [] +    }]      def _get_thumbnail_url(self, uri, itemdoc):          search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail')) @@ -31,8 +29,23 @@ class SouthParkStudiosIE(MTVIE):      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url) +        url = u'http://www.' + mobj.group(u'url')          video_id = mobj.group('id')          webpage = self._download_webpage(url, video_id)          mgid = self._search_regex(r'swfobject.embedSWF\(".*?(mgid:.*?)"',                                    webpage, u'mgid')          return self._get_videos_info(mgid) + +class SouthparkDeIE(SouthParkStudiosIE): +    IE_NAME = u'southpark.de' +    _VALID_URL = r'(https?://)?(www\.)?(?P<url>southpark\.de/(clips|alle-episoden)/(?P<id>.+?)(\?|#|$))' +    _FEED_URL = 'http://www.southpark.de/feeds/video-player/mrss/' + +    _TESTS = [{ +        u'url': u'http://www.southpark.de/clips/uygssh/the-government-wont-respect-my-privacy#tab=featured', +        u'file': u'85487c96-b3b9-4e39-9127-ad88583d9bf2.mp4', +        u'info_dict': { +            u'title': u'The Government Won\'t Respect My Privacy', +            u'description': u'Cartman explains the benefits of "Shitter" to Stan, Kyle and Craig.', +        }, +    }] diff --git a/youtube_dl/extractor/spiegel.py b/youtube_dl/extractor/spiegel.py index 13c86401c..6dc2eda6d 100644 --- a/youtube_dl/extractor/spiegel.py +++ b/youtube_dl/extractor/spiegel.py @@ -2,18 +2,27 @@ import re  import xml.etree.ElementTree  from .common import InfoExtractor +from ..utils import determine_ext  class SpiegelIE(InfoExtractor):      _VALID_URL = r'https?://(?:www\.)?spiegel\.de/video/[^/]*-(?P<videoID>[0-9]+)(?:\.html)?(?:#.*)?$' -    _TEST = { +    _TESTS = [{          u'url': u'http://www.spiegel.de/video/vulkan-tungurahua-in-ecuador-ist-wieder-aktiv-video-1259285.html',          u'file': u'1259285.mp4',          u'md5': u'2c2754212136f35fb4b19767d242f66e',          u'info_dict': {              u"title": u"Vulkanausbruch in Ecuador: Der \"Feuerschlund\" ist wieder aktiv"          } -    } +    }, +    { +        u'url': u'http://www.spiegel.de/video/schach-wm-videoanalyse-des-fuenften-spiels-video-1309159.html', +        u'file': u'1309159.mp4', +        u'md5': u'f2cdf638d7aa47654e251e1aee360af1', +        u'info_dict': { +            u'title': u'Schach-WM in der Videoanalyse: Carlsen nutzt die Fehlgriffe des Titelverteidigers' +        } +    }]      def _real_extract(self, url):          m = re.match(self._VALID_URL, url) @@ -21,25 +30,38 @@ class SpiegelIE(InfoExtractor):          webpage = self._download_webpage(url, video_id) -        video_title = self._html_search_regex(r'<div class="module-title">(.*?)</div>', -            webpage, u'title') +        video_title = self._html_search_regex( +            r'<div class="module-title">(.*?)</div>', webpage, u'title')          xml_url = u'http://video2.spiegel.de/flash/' + video_id + u'.xml' -        xml_code = self._download_webpage(xml_url, video_id, -                    note=u'Downloading XML', errnote=u'Failed to download XML') +        xml_code = self._download_webpage( +            xml_url, video_id, +            note=u'Downloading XML', errnote=u'Failed to download XML')          idoc = xml.etree.ElementTree.fromstring(xml_code) -        last_type = idoc[-1] -        filename = last_type.findall('./filename')[0].text -        duration = float(last_type.findall('./duration')[0].text) -        video_url = 'http://video2.spiegel.de/flash/' + filename -        video_ext = filename.rpartition('.')[2] +        formats = [ +            { +                'format_id': n.tag.rpartition('type')[2], +                'url': u'http://video2.spiegel.de/flash/' + n.find('./filename').text, +                'width': int(n.find('./width').text), +                'height': int(n.find('./height').text), +                'abr': int(n.find('./audiobitrate').text), +                'vbr': int(n.find('./videobitrate').text), +                'vcodec': n.find('./codec').text, +                'acodec': 'MP4A', +            } +            for n in list(idoc) +            # Blacklist type 6, it's extremely LQ and not available on the same server +            if n.tag.startswith('type') and n.tag != 'type6' +        ] +        formats.sort(key=lambda f: f['vbr']) +        duration = float(idoc[0].findall('./duration')[0].text) +          info = {              'id': video_id, -            'url': video_url, -            'ext': video_ext,              'title': video_title,              'duration': duration, +            'formats': formats,          } -        return [info] +        return info diff --git a/youtube_dl/extractor/ted.py b/youtube_dl/extractor/ted.py index 76cfdfb90..2e497c86e 100644 --- a/youtube_dl/extractor/ted.py +++ b/youtube_dl/extractor/ted.py @@ -43,26 +43,25 @@ class TEDIE(SubtitlesInfoExtractor):              self.to_screen(u'Getting info of playlist %s: "%s"' % (playlist_id,name))              return [self._playlist_videos_info(url,name,playlist_id)] -    def _playlist_videos_info(self,url,name,playlist_id=0): + +    def _playlist_videos_info(self, url, name, playlist_id):          '''Returns the videos of the playlist''' -        video_RE=r''' -                     <li\ id="talk_(\d+)"([.\s]*?)data-id="(?P<video_id>\d+)" -                     ([.\s]*?)data-playlist_item_id="(\d+)" -                     ([.\s]*?)data-mediaslug="(?P<mediaSlug>.+?)" -                     ''' -        video_name_RE=r'<p\ class="talk-title"><a href="(?P<talk_url>/talks/(.+).html)">(?P<fullname>.+?)</a></p>' -        webpage=self._download_webpage(url, playlist_id, 'Downloading playlist webpage') -        m_videos=re.finditer(video_RE,webpage,re.VERBOSE) -        m_names=re.finditer(video_name_RE,webpage) + +        webpage = self._download_webpage( +            url, playlist_id, u'Downloading playlist webpage') +        matches = re.finditer( +            r'<p\s+class="talk-title[^"]*"><a\s+href="(?P<talk_url>/talks/[^"]+\.html)">[^<]*</a></p>', +            webpage)          playlist_title = self._html_search_regex(r'div class="headline">\s*?<h1>\s*?<span>(.*?)</span>',                                                   webpage, 'playlist title') -        playlist_entries = [] -        for m_video, m_name in zip(m_videos,m_names): -            talk_url='http://www.ted.com%s' % m_name.group('talk_url') -            playlist_entries.append(self.url_result(talk_url, 'TED')) -        return self.playlist_result(playlist_entries, playlist_id = playlist_id, playlist_title = playlist_title) +        playlist_entries = [ +            self.url_result(u'http://www.ted.com' + m.group('talk_url'), 'TED') +            for m in matches +        ] +        return self.playlist_result( +            playlist_entries, playlist_id=playlist_id, playlist_title=playlist_title)      def _talk_info(self, url, video_id=0):          """Return the video for the talk in the url""" @@ -85,7 +84,7 @@ class TEDIE(SubtitlesInfoExtractor):              'ext': 'mp4',              'url': stream['file'],              'format': stream['id'] -            } for stream in info['htmlStreams']] +        } for stream in info['htmlStreams']]          video_id = info['id'] @@ -95,7 +94,7 @@ class TEDIE(SubtitlesInfoExtractor):              self._list_available_subtitles(video_id, webpage)              return -        info = { +        return {              'id': video_id,              'title': title,              'thumbnail': thumbnail, @@ -104,11 +103,6 @@ class TEDIE(SubtitlesInfoExtractor):              'formats': formats,          } -        # TODO: Remove when #980 has been merged -        info.update(info['formats'][-1]) - -        return info -      def _get_available_subtitles(self, video_id, webpage):          try:              options = self._search_regex(r'(?:<select name="subtitles_language_select" id="subtitles_language_select">)(.*?)(?:</select>)', webpage, 'subtitles_language_select', flags=re.DOTALL) diff --git a/youtube_dl/extractor/toutv.py b/youtube_dl/extractor/toutv.py new file mode 100644 index 000000000..73ea67da9 --- /dev/null +++ b/youtube_dl/extractor/toutv.py @@ -0,0 +1,75 @@ +# coding: utf-8 +import re +import xml.etree.ElementTree + +from .common import InfoExtractor +from ..utils import ( +    ExtractorError, +    unified_strdate, +) + + +class TouTvIE(InfoExtractor): +    IE_NAME = u'tou.tv' +    _VALID_URL = r'https?://www\.tou\.tv/(?P<id>[a-zA-Z0-9_-]+(?:/(?P<episode>S[0-9]+E[0-9]+)))' + +    _TEST = { +        u'url': u'http://www.tou.tv/30-vies/S04E41', +        u'file': u'30-vies_S04E41.mp4', +        u'info_dict': { +            u'title': u'30 vies Saison 4 / Épisode 41', +            u'description': u'md5:da363002db82ccbe4dafeb9cab039b09', +            u'age_limit': 8, +            u'uploader': u'Groupe des Nouveaux Médias', +            u'duration': 1296, +            u'upload_date': u'20131118', +            u'thumbnail': u'http://static.tou.tv/medias/images/2013-11-18_19_00_00_30VIES_0341_01_L.jpeg', +        }, +        u'params': { +            u'skip_download': True,  # Requires rtmpdump +        }, +        u'xskip': 'Only available in Canada' +    } + +    def _real_extract(self, url): +        mobj = re.match(self._VALID_URL, url) +        video_id = mobj.group('id') +        webpage = self._download_webpage(url, video_id) + +        mediaId = self._search_regex( +            r'"idMedia":\s*"([^"]+)"', webpage, u'media ID') + +        # TODO test from de +        streams_url = u'http://release.theplatform.com/content.select?pid=' + mediaId +        streams_webpage = self._download_webpage( +            streams_url, video_id, note=u'Downloading stream list') + +        streams_doc = xml.etree.ElementTree.fromstring( +            streams_webpage.encode('utf-8')) +        video_url = next(n.text +                         for n in streams_doc.findall('.//choice/url') +                         if u'//ad.doubleclick' not in n.text) +        if video_url.endswith('/Unavailable.flv'): +            raise ExtractorError( +                u'Access to this video is blocked from outside of Canada', +                expected=True) + +        duration_str = self._html_search_meta( +            'video:duration', webpage, u'duration') +        duration = int(duration_str) if duration_str else None +        upload_date_str = self._html_search_meta( +            'video:release_date', webpage, u'upload date') +        upload_date = unified_strdate(upload_date_str) if upload_date_str else None + +        return { +            'id': video_id, +            'title': self._og_search_title(webpage), +            'url': video_url, +            'description': self._og_search_description(webpage), +            'uploader': self._dc_search_uploader(webpage), +            'thumbnail': self._og_search_thumbnail(webpage), +            'age_limit': self._media_rating_search(webpage), +            'duration': duration, +            'upload_date': upload_date, +            'ext': 'mp4', +        } diff --git a/youtube_dl/extractor/tvp.py b/youtube_dl/extractor/tvp.py index 32e0f5037..bfed9dd04 100644 --- a/youtube_dl/extractor/tvp.py +++ b/youtube_dl/extractor/tvp.py @@ -13,9 +13,10 @@ class TvpIE(InfoExtractor):          u'md5': u'148408967a6a468953c0a75cbdaf0d7a',          u'file': u'12878238.wmv',          u'info_dict': { -            u'title': u'31.10.2013', -            u'description': u'31.10.2013', +            u'title': u'31.10.2013 - Odcinek 2', +            u'description': u'31.10.2013 - Odcinek 2',          }, +        u'skip': u'Download has to use same server IP as extraction. Therefore, a good (load-balancing) DNS resolver will make the download fail.'      }      def _real_extract(self, url): diff --git a/youtube_dl/extractor/vevo.py b/youtube_dl/extractor/vevo.py index 3f6020f74..4378b1780 100644 --- a/youtube_dl/extractor/vevo.py +++ b/youtube_dl/extractor/vevo.py @@ -78,12 +78,13 @@ class VevoIE(InfoExtractor):                  continue              format_url = self._SMIL_BASE_URL + m.group('path') -            format_note = ('%(vcodec)s@%(vbr)4sk, %(acodec)s@%(abr)3sk' % -                           m.groupdict())              formats.append({                  'url': format_url,                  'format_id': u'SMIL_' + m.group('cbr'), -                'format_note': format_note, +                'vcodec': m.group('vcodec'), +                'acodec': m.group('acodec'), +                'vbr': int(m.group('vbr')), +                'abr': int(m.group('abr')),                  'ext': m.group('ext'),                  'width': int(m.group('width')),                  'height': int(m.group('height')), diff --git a/youtube_dl/extractor/youtube.py b/youtube_dl/extractor/youtube.py index 56d30f52a..41838237c 100644 --- a/youtube_dl/extractor/youtube.py +++ b/youtube_dl/extractor/youtube.py @@ -139,10 +139,10 @@ class YoutubeBaseInfoExtractor(InfoExtractor):  class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):      IE_DESC = u'YouTube.com' -    _VALID_URL = r"""^ +    _VALID_URL = r"""(?x)^                       ( -                         (?:https?://)?                                       # http(s):// (optional) -                         (?:(?:(?:(?:\w+\.)?youtube(?:-nocookie)?\.com/| +                         (?:https?://|//)?                                    # http(s):// or protocol-independent URL (optional) +                         (?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|                              tube\.majestyc\.net/|                              youtube\.googleapis\.com/)                        # the various hostnames, with wildcard subdomains                           (?:.*?\#/)?                                          # handle anchor (#/) redirect urls @@ -363,6 +363,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):                  u"uploader_id": u"justintimberlakeVEVO"              }          }, +        { +            u"url":  u"//www.YouTube.com/watch?v=yZIXLfi8CZQ", +            u"file":  u"yZIXLfi8CZQ.mp4", +            u"note": u"Embed-only video (#1746)", +            u"info_dict": { +                u"upload_date": u"20120608", +                u"title": u"Principal Sexually Assaults A Teacher - Episode 117 - 8th June 2012", +                u"description": u"md5:09b78bd971f1e3e289601dfba15ca4f7", +                u"uploader": u"SET India", +                u"uploader_id": u"setindia" +            } +        },      ] @@ -370,7 +382,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):      def suitable(cls, url):          """Receives a URL and returns True if suitable for this IE."""          if YoutubePlaylistIE.suitable(url): return False -        return re.match(cls._VALID_URL, url, re.VERBOSE) is not None +        return re.match(cls._VALID_URL, url) is not None      def __init__(self, *args, **kwargs):          super(YoutubeIE, self).__init__(*args, **kwargs) @@ -1019,6 +1031,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):          """Turn the encrypted s field into a working signature"""          if player_url is not None: +            if player_url.startswith(u'//'): +                player_url = u'https:' + player_url              try:                  player_id = (player_url, len(s))                  if player_id not in self._player_cache: @@ -1270,7 +1284,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):              # We simulate the access to the video from www.youtube.com/v/{video_id}              # this can be viewed without login into Youtube              data = compat_urllib_parse.urlencode({'video_id': video_id, -                                                  'el': 'embedded', +                                                  'el': 'player_embedded',                                                    'gl': 'US',                                                    'hl': 'en',                                                    'eurl': 'https://youtube.googleapis.com/v/' + video_id, @@ -1299,6 +1313,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):              else:                  raise ExtractorError(u'"token" parameter not in video info for unknown reason') +        if 'view_count' in video_info: +            view_count = int(video_info['view_count'][0]) +        else: +            view_count = None +          # Check for "rental" videos          if 'ypc_video_rental_bar_text' in video_info and 'author' not in video_info:              raise ExtractorError(u'"rental" videos not supported') @@ -1487,6 +1506,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):                  'age_limit':    18 if age_gate else 0,                  'annotations':  video_annotations,                  'webpage_url': 'https://www.youtube.com/watch?v=%s' % video_id, +                'view_count': view_count,              })          return results @@ -1592,20 +1612,31 @@ class YoutubeChannelIE(InfoExtractor):          # Download channel page          channel_id = mobj.group(1)          video_ids = [] +        url = 'https://www.youtube.com/channel/%s/videos' % channel_id +        channel_page = self._download_webpage(url, channel_id) +        if re.search(r'channel-header-autogenerated-label', channel_page) is not None: +            autogenerated = True +        else: +            autogenerated = False -        # Download all channel pages using the json-based channel_ajax query -        for pagenum in itertools.count(1): -            url = self._MORE_PAGES_URL % (pagenum, channel_id) -            page = self._download_webpage(url, channel_id, -                                          u'Downloading page #%s' % pagenum) - -            page = json.loads(page) - -            ids_in_page = self.extract_videos_from_page(page['content_html']) -            video_ids.extend(ids_in_page) - -            if self._MORE_PAGES_INDICATOR not in page['load_more_widget_html']: -                break +        if autogenerated: +            # The videos are contained in a single page +            # the ajax pages can't be used, they are empty +            video_ids = self.extract_videos_from_page(channel_page) +        else: +            # Download all channel pages using the json-based channel_ajax query +            for pagenum in itertools.count(1): +                url = self._MORE_PAGES_URL % (pagenum, channel_id) +                page = self._download_webpage(url, channel_id, +                                              u'Downloading page #%s' % pagenum) +     +                page = json.loads(page) +     +                ids_in_page = self.extract_videos_from_page(page['content_html']) +                video_ids.extend(ids_in_page) +     +                if self._MORE_PAGES_INDICATOR not in page['load_more_widget_html']: +                    break          self._downloader.to_screen(u'[youtube] Channel %s: Found %i videos' % (channel_id, len(video_ids)))  | 
