mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-11-04 00:25:15 +00:00 
			
		
		
		
	[turner,nba,cnn,adultswim] add base extractor to parse cvp feeds
This commit is contained in:
		@@ -3,16 +3,11 @@ from __future__ import unicode_literals
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .turner import TurnerBaseIE
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import ExtractorError
 | 
				
			||||||
    determine_ext,
 | 
					 | 
				
			||||||
    ExtractorError,
 | 
					 | 
				
			||||||
    float_or_none,
 | 
					 | 
				
			||||||
    xpath_text,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdultSwimIE(InfoExtractor):
 | 
					class AdultSwimIE(TurnerBaseIE):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?adultswim\.com/videos/(?P<is_playlist>playlists/)?(?P<show_path>[^/]+)/(?P<episode_path>[^/?#]+)/?'
 | 
					    _VALID_URL = r'https?://(?:www\.)?adultswim\.com/videos/(?P<is_playlist>playlists/)?(?P<show_path>[^/]+)/(?P<episode_path>[^/?#]+)/?'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
@@ -96,7 +91,8 @@ class AdultSwimIE(InfoExtractor):
 | 
				
			|||||||
        'params': {
 | 
					        'params': {
 | 
				
			||||||
            # m3u8 download
 | 
					            # m3u8 download
 | 
				
			||||||
            'skip_download': True,
 | 
					            'skip_download': True,
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        'expected_warnings': ['Unable to download f4m manifest'],
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -176,57 +172,23 @@ class AdultSwimIE(InfoExtractor):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        entries = []
 | 
					        entries = []
 | 
				
			||||||
        for part_num, segment_id in enumerate(segment_ids):
 | 
					        for part_num, segment_id in enumerate(segment_ids):
 | 
				
			||||||
            segment_url = 'http://www.adultswim.com/videos/api/v0/assets?id=%s&platform=desktop' % segment_id
 | 
					            segement_info = self._extract_cvp_info(
 | 
				
			||||||
 | 
					                'http://www.adultswim.com/videos/api/v0/assets?id=%s&platform=desktop' % segment_id,
 | 
				
			||||||
 | 
					                segment_id, {
 | 
				
			||||||
 | 
					                    'secure': {
 | 
				
			||||||
 | 
					                        'media_src': 'http://androidhls-secure.cdn.turner.com/adultswim/big',
 | 
				
			||||||
 | 
					                        'tokenizer_src': 'http://www.adultswim.com/astv/mvpd/processors/services/token_ipadAdobe.do',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
            segment_title = '%s - %s' % (show_title, episode_title)
 | 
					            segment_title = '%s - %s' % (show_title, episode_title)
 | 
				
			||||||
            if len(segment_ids) > 1:
 | 
					            if len(segment_ids) > 1:
 | 
				
			||||||
                segment_title += ' Part %d' % (part_num + 1)
 | 
					                segment_title += ' Part %d' % (part_num + 1)
 | 
				
			||||||
 | 
					            segement_info.update({
 | 
				
			||||||
            idoc = self._download_xml(
 | 
					 | 
				
			||||||
                segment_url, segment_title,
 | 
					 | 
				
			||||||
                'Downloading segment information', 'Unable to download segment information')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            segment_duration = float_or_none(
 | 
					 | 
				
			||||||
                xpath_text(idoc, './/trt', 'segment duration').strip())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            formats = []
 | 
					 | 
				
			||||||
            file_els = idoc.findall('.//files/file') or idoc.findall('./files/file')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            unique_urls = []
 | 
					 | 
				
			||||||
            unique_file_els = []
 | 
					 | 
				
			||||||
            for file_el in file_els:
 | 
					 | 
				
			||||||
                media_url = file_el.text
 | 
					 | 
				
			||||||
                if not media_url or determine_ext(media_url) == 'f4m':
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                if file_el.text not in unique_urls:
 | 
					 | 
				
			||||||
                    unique_urls.append(file_el.text)
 | 
					 | 
				
			||||||
                    unique_file_els.append(file_el)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for file_el in unique_file_els:
 | 
					 | 
				
			||||||
                bitrate = file_el.attrib.get('bitrate')
 | 
					 | 
				
			||||||
                ftype = file_el.attrib.get('type')
 | 
					 | 
				
			||||||
                media_url = file_el.text
 | 
					 | 
				
			||||||
                if determine_ext(media_url) == 'm3u8':
 | 
					 | 
				
			||||||
                    formats.extend(self._extract_m3u8_formats(
 | 
					 | 
				
			||||||
                        media_url, segment_title, 'mp4', preference=0,
 | 
					 | 
				
			||||||
                        m3u8_id='hls', fatal=False))
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    formats.append({
 | 
					 | 
				
			||||||
                        'format_id': '%s_%s' % (bitrate, ftype),
 | 
					 | 
				
			||||||
                        'url': file_el.text.strip(),
 | 
					 | 
				
			||||||
                        # The bitrate may not be a number (for example: 'iphone')
 | 
					 | 
				
			||||||
                        'tbr': int(bitrate) if bitrate.isdigit() else None,
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self._sort_formats(formats)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            entries.append({
 | 
					 | 
				
			||||||
                'id': segment_id,
 | 
					                'id': segment_id,
 | 
				
			||||||
                'title': segment_title,
 | 
					                'title': segment_title,
 | 
				
			||||||
                'formats': formats,
 | 
					                'description': episode_description,
 | 
				
			||||||
                'duration': segment_duration,
 | 
					 | 
				
			||||||
                'description': episode_description
 | 
					 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					            entries.append(segement_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            '_type': 'playlist',
 | 
					            '_type': 'playlist',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,11 @@ from __future__ import unicode_literals
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..utils import (
 | 
					from .turner import TurnerBaseIE
 | 
				
			||||||
    int_or_none,
 | 
					from ..utils import url_basename
 | 
				
			||||||
    parse_duration,
 | 
					 | 
				
			||||||
    url_basename,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CNNIE(InfoExtractor):
 | 
					class CNNIE(TurnerBaseIE):
 | 
				
			||||||
    _VALID_URL = r'''(?x)https?://(?:(?P<sub_domain>edition|www|money)\.)?cnn\.com/(?:video/(?:data/.+?|\?)/)?videos?/
 | 
					    _VALID_URL = r'''(?x)https?://(?:(?P<sub_domain>edition|www|money)\.)?cnn\.com/(?:video/(?:data/.+?|\?)/)?videos?/
 | 
				
			||||||
        (?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:[a-z\-]+)|(?=&)))'''
 | 
					        (?P<path>.+?/(?P<title>[^/]+?)(?:\.(?:[a-z\-]+)|(?=&)))'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,43 +15,50 @@ class CNNIE(InfoExtractor):
 | 
				
			|||||||
        'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
 | 
					        'url': 'http://edition.cnn.com/video/?/video/sports/2013/06/09/nadal-1-on-1.cnn',
 | 
				
			||||||
        'md5': '3e6121ea48df7e2259fe73a0628605c4',
 | 
					        'md5': '3e6121ea48df7e2259fe73a0628605c4',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'sports/2013/06/09/nadal-1-on-1.cnn',
 | 
					            'id': 'nadal-1-on-1',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Nadal wins 8th French Open title',
 | 
					            'title': 'Nadal wins 8th French Open title',
 | 
				
			||||||
            'description': 'World Sport\'s Amanda Davies chats with 2013 French Open champion Rafael Nadal.',
 | 
					            'description': 'World Sport\'s Amanda Davies chats with 2013 French Open champion Rafael Nadal.',
 | 
				
			||||||
            'duration': 135,
 | 
					            'duration': 135,
 | 
				
			||||||
            'upload_date': '20130609',
 | 
					            'upload_date': '20130609',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'expected_warnings': ['Failed to download m3u8 information'],
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://edition.cnn.com/video/?/video/us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology&utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+rss%2Fcnn_topstories+%28RSS%3A+Top+Stories%29',
 | 
					        'url': 'http://edition.cnn.com/video/?/video/us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology&utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+rss%2Fcnn_topstories+%28RSS%3A+Top+Stories%29',
 | 
				
			||||||
        'md5': 'b5cc60c60a3477d185af8f19a2a26f4e',
 | 
					        'md5': 'b5cc60c60a3477d185af8f19a2a26f4e',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology',
 | 
					            'id': 'sot-student-gives-epic-speech',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': "Student's epic speech stuns new freshmen",
 | 
					            'title': "Student's epic speech stuns new freshmen",
 | 
				
			||||||
            'description': "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
 | 
					            'description': "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
 | 
				
			||||||
            'upload_date': '20130821',
 | 
					            'upload_date': '20130821',
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        'expected_warnings': ['Failed to download m3u8 information'],
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.cnn.com/video/data/2.0/video/living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln.html',
 | 
					        'url': 'http://www.cnn.com/video/data/2.0/video/living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln.html',
 | 
				
			||||||
        'md5': 'f14d02ebd264df951feb2400e2c25a1b',
 | 
					        'md5': 'f14d02ebd264df951feb2400e2c25a1b',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln',
 | 
					            'id': 'growing-america-nashville-salemtown-board-episode-1',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Nashville Ep. 1: Hand crafted skateboards',
 | 
					            'title': 'Nashville Ep. 1: Hand crafted skateboards',
 | 
				
			||||||
            'description': 'md5:e7223a503315c9f150acac52e76de086',
 | 
					            'description': 'md5:e7223a503315c9f150acac52e76de086',
 | 
				
			||||||
            'upload_date': '20141222',
 | 
					            'upload_date': '20141222',
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        'expected_warnings': ['Failed to download m3u8 information'],
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://money.cnn.com/video/news/2016/08/19/netflix-stunning-stats.cnnmoney/index.html',
 | 
					        'url': 'http://money.cnn.com/video/news/2016/08/19/netflix-stunning-stats.cnnmoney/index.html',
 | 
				
			||||||
        'md5': '52a515dc1b0f001cd82e4ceda32be9d1',
 | 
					        'md5': '52a515dc1b0f001cd82e4ceda32be9d1',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '/video/news/2016/08/19/netflix-stunning-stats.cnnmoney',
 | 
					            'id': 'netflix-stunning-stats',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': '5 stunning stats about Netflix',
 | 
					            'title': '5 stunning stats about Netflix',
 | 
				
			||||||
            'description': 'Did you know that Netflix has more than 80 million members? Here are five facts about the online video distributor that you probably didn\'t know.',
 | 
					            'description': 'Did you know that Netflix has more than 80 million members? Here are five facts about the online video distributor that you probably didn\'t know.',
 | 
				
			||||||
            'upload_date': '20160819',
 | 
					            'upload_date': '20160819',
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {
 | 
				
			||||||
 | 
					            # m3u8 download
 | 
				
			||||||
 | 
					            'skip_download': True,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://cnn.com/video/?/video/politics/2015/03/27/pkg-arizona-senator-church-attendance-mandatory.ktvk',
 | 
					        'url': 'http://cnn.com/video/?/video/politics/2015/03/27/pkg-arizona-senator-church-attendance-mandatory.ktvk',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
@@ -84,67 +88,12 @@ class CNNIE(InfoExtractor):
 | 
				
			|||||||
        if sub_domain not in ('money', 'edition'):
 | 
					        if sub_domain not in ('money', 'edition'):
 | 
				
			||||||
            sub_domain = 'edition'
 | 
					            sub_domain = 'edition'
 | 
				
			||||||
        config = self._CONFIG[sub_domain]
 | 
					        config = self._CONFIG[sub_domain]
 | 
				
			||||||
        info_url = config['data_src'] % path
 | 
					        return self._extract_cvp_info(
 | 
				
			||||||
        info = self._download_xml(info_url, page_title)
 | 
					            config['data_src'] % path, page_title, {
 | 
				
			||||||
 | 
					                'default': {
 | 
				
			||||||
        formats = []
 | 
					                    'media_src': config['media_src'],
 | 
				
			||||||
        rex = re.compile(r'''(?x)
 | 
					                }
 | 
				
			||||||
            (?P<width>[0-9]+)x(?P<height>[0-9]+)
 | 
					            })
 | 
				
			||||||
            (?:_(?P<bitrate>[0-9]+)k)?
 | 
					 | 
				
			||||||
        ''')
 | 
					 | 
				
			||||||
        for f in info.findall('files/file'):
 | 
					 | 
				
			||||||
            video_url = config['media_src'] + f.text.strip()
 | 
					 | 
				
			||||||
            fdct = {
 | 
					 | 
				
			||||||
                'format_id': f.attrib['bitrate'],
 | 
					 | 
				
			||||||
                'url': video_url,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mf = rex.match(f.attrib['bitrate'])
 | 
					 | 
				
			||||||
            if mf:
 | 
					 | 
				
			||||||
                fdct['width'] = int(mf.group('width'))
 | 
					 | 
				
			||||||
                fdct['height'] = int(mf.group('height'))
 | 
					 | 
				
			||||||
                fdct['tbr'] = int_or_none(mf.group('bitrate'))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                mf = rex.search(f.text)
 | 
					 | 
				
			||||||
                if mf:
 | 
					 | 
				
			||||||
                    fdct['width'] = int(mf.group('width'))
 | 
					 | 
				
			||||||
                    fdct['height'] = int(mf.group('height'))
 | 
					 | 
				
			||||||
                    fdct['tbr'] = int_or_none(mf.group('bitrate'))
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    mi = re.match(r'ios_(audio|[0-9]+)$', f.attrib['bitrate'])
 | 
					 | 
				
			||||||
                    if mi:
 | 
					 | 
				
			||||||
                        if mi.group(1) == 'audio':
 | 
					 | 
				
			||||||
                            fdct['vcodec'] = 'none'
 | 
					 | 
				
			||||||
                            fdct['ext'] = 'm4a'
 | 
					 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            fdct['tbr'] = int(mi.group(1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            formats.append(fdct)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._sort_formats(formats)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        thumbnails = [{
 | 
					 | 
				
			||||||
            'height': int(t.attrib['height']),
 | 
					 | 
				
			||||||
            'width': int(t.attrib['width']),
 | 
					 | 
				
			||||||
            'url': t.text,
 | 
					 | 
				
			||||||
        } for t in info.findall('images/image')]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        metas_el = info.find('metas')
 | 
					 | 
				
			||||||
        upload_date = (
 | 
					 | 
				
			||||||
            metas_el.attrib.get('version') if metas_el is not None else None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        duration_el = info.find('length')
 | 
					 | 
				
			||||||
        duration = parse_duration(duration_el.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'id': info.attrib['id'],
 | 
					 | 
				
			||||||
            'title': info.find('headline').text,
 | 
					 | 
				
			||||||
            'formats': formats,
 | 
					 | 
				
			||||||
            'thumbnails': thumbnails,
 | 
					 | 
				
			||||||
            'description': info.find('description').text,
 | 
					 | 
				
			||||||
            'duration': duration,
 | 
					 | 
				
			||||||
            'upload_date': upload_date,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CNNBlogsIE(InfoExtractor):
 | 
					class CNNBlogsIE(InfoExtractor):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,20 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import functools
 | 
					import functools
 | 
				
			||||||
import os.path
 | 
					 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .turner import TurnerBaseIE
 | 
				
			||||||
from ..compat import (
 | 
					from ..compat import (
 | 
				
			||||||
    compat_urllib_parse_urlencode,
 | 
					    compat_urllib_parse_urlencode,
 | 
				
			||||||
    compat_urlparse,
 | 
					    compat_urlparse,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    int_or_none,
 | 
					 | 
				
			||||||
    OnDemandPagedList,
 | 
					    OnDemandPagedList,
 | 
				
			||||||
    parse_duration,
 | 
					 | 
				
			||||||
    remove_start,
 | 
					    remove_start,
 | 
				
			||||||
    xpath_text,
 | 
					 | 
				
			||||||
    xpath_attr,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NBAIE(InfoExtractor):
 | 
					class NBAIE(TurnerBaseIE):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:watch\.|www\.)?nba\.com/(?P<path>(?:[^/]+/)+(?P<id>[^?]*?))/?(?:/index\.html)?(?:\?.*)?$'
 | 
					    _VALID_URL = r'https?://(?:watch\.|www\.)?nba\.com/(?P<path>(?:[^/]+/)+(?P<id>[^?]*?))/?(?:/index\.html)?(?:\?.*)?$'
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html',
 | 
					        'url': 'http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html',
 | 
				
			||||||
@@ -59,7 +54,7 @@ class NBAIE(InfoExtractor):
 | 
				
			|||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Practice: Doc Rivers - 2/16/16',
 | 
					            'title': 'Practice: Doc Rivers - 2/16/16',
 | 
				
			||||||
            'description': 'Head Coach Doc Rivers addresses the media following practice.',
 | 
					            'description': 'Head Coach Doc Rivers addresses the media following practice.',
 | 
				
			||||||
            'upload_date': '20160217',
 | 
					            'upload_date': '20160216',
 | 
				
			||||||
            'timestamp': 1455672000,
 | 
					            'timestamp': 1455672000,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'params': {
 | 
					        'params': {
 | 
				
			||||||
@@ -80,7 +75,7 @@ class NBAIE(InfoExtractor):
 | 
				
			|||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.nba.com/timberwolves/wiggins-shootaround#',
 | 
					        'url': 'http://www.nba.com/timberwolves/wiggins-shootaround#',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'Wigginsmp4',
 | 
					            'id': 'Wigginsmp4-3462601',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Shootaround Access - Dec. 12 | Andrew Wiggins',
 | 
					            'title': 'Shootaround Access - Dec. 12 | Andrew Wiggins',
 | 
				
			||||||
            'description': 'Wolves rookie Andrew Wiggins addresses the media after Friday\'s shootaround.',
 | 
					            'description': 'Wolves rookie Andrew Wiggins addresses the media after Friday\'s shootaround.',
 | 
				
			||||||
@@ -145,53 +140,12 @@ class NBAIE(InfoExtractor):
 | 
				
			|||||||
            if path.startswith('video/teams'):
 | 
					            if path.startswith('video/teams'):
 | 
				
			||||||
                path = 'video/channels/proxy/' + path[6:]
 | 
					                path = 'video/channels/proxy/' + path[6:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video_info = self._download_xml('http://www.nba.com/%s.xml' % path, video_id)
 | 
					        return self._extract_cvp_info(
 | 
				
			||||||
        video_id = os.path.splitext(xpath_text(video_info, 'slug'))[0]
 | 
					            'http://www.nba.com/%s.xml' % path, video_id, {
 | 
				
			||||||
        title = xpath_text(video_info, 'headline')
 | 
					                'default': {
 | 
				
			||||||
        description = xpath_text(video_info, 'description')
 | 
					                    'media_src': 'http://nba.cdn.turner.com/nba/big',
 | 
				
			||||||
        duration = parse_duration(xpath_text(video_info, 'length'))
 | 
					                },
 | 
				
			||||||
        timestamp = int_or_none(xpath_attr(video_info, 'dateCreated', 'uts'))
 | 
					                'm3u8': {
 | 
				
			||||||
 | 
					                    'media_src': 'http://nbavod-f.akamaihd.net',
 | 
				
			||||||
        thumbnails = []
 | 
					                },
 | 
				
			||||||
        for image in video_info.find('images'):
 | 
					 | 
				
			||||||
            thumbnails.append({
 | 
					 | 
				
			||||||
                'id': image.attrib.get('cut'),
 | 
					 | 
				
			||||||
                'url': image.text,
 | 
					 | 
				
			||||||
                'width': int_or_none(image.attrib.get('width')),
 | 
					 | 
				
			||||||
                'height': int_or_none(image.attrib.get('height')),
 | 
					 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 | 
					 | 
				
			||||||
        formats = []
 | 
					 | 
				
			||||||
        for video_file in video_info.findall('.//file'):
 | 
					 | 
				
			||||||
            video_url = video_file.text
 | 
					 | 
				
			||||||
            if video_url.startswith('/'):
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            if video_url.endswith('.m3u8'):
 | 
					 | 
				
			||||||
                formats.extend(self._extract_m3u8_formats(video_url, video_id, ext='mp4', m3u8_id='hls', fatal=False))
 | 
					 | 
				
			||||||
            elif video_url.endswith('.f4m'):
 | 
					 | 
				
			||||||
                formats.extend(self._extract_f4m_formats(video_url + '?hdcore=3.4.1.1', video_id, f4m_id='hds', fatal=False))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                key = video_file.attrib.get('bitrate')
 | 
					 | 
				
			||||||
                format_info = {
 | 
					 | 
				
			||||||
                    'format_id': key,
 | 
					 | 
				
			||||||
                    'url': video_url,
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                mobj = re.search(r'(\d+)x(\d+)(?:_(\d+))?', key)
 | 
					 | 
				
			||||||
                if mobj:
 | 
					 | 
				
			||||||
                    format_info.update({
 | 
					 | 
				
			||||||
                        'width': int(mobj.group(1)),
 | 
					 | 
				
			||||||
                        'height': int(mobj.group(2)),
 | 
					 | 
				
			||||||
                        'tbr': int_or_none(mobj.group(3)),
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                formats.append(format_info)
 | 
					 | 
				
			||||||
        self._sort_formats(formats)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'id': video_id,
 | 
					 | 
				
			||||||
            'title': title,
 | 
					 | 
				
			||||||
            'description': description,
 | 
					 | 
				
			||||||
            'duration': duration,
 | 
					 | 
				
			||||||
            'timestamp': timestamp,
 | 
					 | 
				
			||||||
            'thumbnails': thumbnails,
 | 
					 | 
				
			||||||
            'formats': formats,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										163
									
								
								youtube_dl/extractor/turner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								youtube_dl/extractor/turner.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
				
			|||||||
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .common import InfoExtractor
 | 
				
			||||||
 | 
					from ..utils import (
 | 
				
			||||||
 | 
					    xpath_text,
 | 
				
			||||||
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    determine_ext,
 | 
				
			||||||
 | 
					    parse_duration,
 | 
				
			||||||
 | 
					    xpath_attr,
 | 
				
			||||||
 | 
					    update_url_query,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TurnerBaseIE(InfoExtractor):
 | 
				
			||||||
 | 
					    def _extract_cvp_info(self, data_src, video_id, path_data={}):
 | 
				
			||||||
 | 
					        video_data = self._download_xml(data_src, video_id)
 | 
				
			||||||
 | 
					        video_id = video_data.attrib['id'].split('/')[-1].split('.')[0]
 | 
				
			||||||
 | 
					        title = xpath_text(video_data, 'headline', fatal=True)
 | 
				
			||||||
 | 
					        # rtmp_src = xpath_text(video_data, 'akamai/src')
 | 
				
			||||||
 | 
					        # if rtmp_src:
 | 
				
			||||||
 | 
					        #     splited_rtmp_src = rtmp_src.split(',')
 | 
				
			||||||
 | 
					        #     if len(splited_rtmp_src) == 2:
 | 
				
			||||||
 | 
					        #         rtmp_src = splited_rtmp_src[1]
 | 
				
			||||||
 | 
					        # aifp = xpath_text(video_data, 'akamai/aifp', default='')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tokens = {}
 | 
				
			||||||
 | 
					        urls = []
 | 
				
			||||||
 | 
					        formats = []
 | 
				
			||||||
 | 
					        rex = re.compile(r'''(?x)
 | 
				
			||||||
 | 
					            (?P<width>[0-9]+)x(?P<height>[0-9]+)
 | 
				
			||||||
 | 
					            (?:_(?P<bitrate>[0-9]+))?
 | 
				
			||||||
 | 
					        ''')
 | 
				
			||||||
 | 
					        for video_file in video_data.findall('files/file'):
 | 
				
			||||||
 | 
					            video_url = video_file.text.strip()
 | 
				
			||||||
 | 
					            if not video_url:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            ext = determine_ext(video_url)
 | 
				
			||||||
 | 
					            if video_url.startswith('/mp4:protected/'):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					                # TODO Correct extraction for these files
 | 
				
			||||||
 | 
					                # protected_path_data = path_data.get('protected')
 | 
				
			||||||
 | 
					                # if not protected_path_data or not rtmp_src:
 | 
				
			||||||
 | 
					                #     continue
 | 
				
			||||||
 | 
					                # protected_path = self._search_regex(
 | 
				
			||||||
 | 
					                #     r'/mp4:(.+)\.[a-z0-9]', video_url, 'secure path')
 | 
				
			||||||
 | 
					                # auth = self._download_webpage(
 | 
				
			||||||
 | 
					                #     protected_path_data['tokenizer_src'], query={
 | 
				
			||||||
 | 
					                #         'path': protected_path,
 | 
				
			||||||
 | 
					                #         'videoId': video_id,
 | 
				
			||||||
 | 
					                #         'aifp': aifp,
 | 
				
			||||||
 | 
					                #     })
 | 
				
			||||||
 | 
					                # token = xpath_text(auth, 'token')
 | 
				
			||||||
 | 
					                # if not token:
 | 
				
			||||||
 | 
					                #     continue
 | 
				
			||||||
 | 
					                # video_url = rtmp_src + video_url + '?' + token
 | 
				
			||||||
 | 
					            elif video_url.startswith('/secure/'):
 | 
				
			||||||
 | 
					                secure_path_data = path_data.get('secure')
 | 
				
			||||||
 | 
					                if not secure_path_data:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                video_url = secure_path_data['media_src'] + video_url
 | 
				
			||||||
 | 
					                secure_path = self._search_regex(r'https?://[^/]+(.+/)', video_url, 'secure path') + '*'
 | 
				
			||||||
 | 
					                token = tokens.get(secure_path)
 | 
				
			||||||
 | 
					                if not token:
 | 
				
			||||||
 | 
					                    auth = self._download_xml(
 | 
				
			||||||
 | 
					                        secure_path_data['tokenizer_src'], video_id, query={
 | 
				
			||||||
 | 
					                            'path': secure_path,
 | 
				
			||||||
 | 
					                            'videoId': video_id,
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    token = xpath_text(auth, 'token')
 | 
				
			||||||
 | 
					                    if not token:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                    tokens[secure_path] = token
 | 
				
			||||||
 | 
					                video_url = video_url + '?hdnea=' + token
 | 
				
			||||||
 | 
					            elif not re.match('https?://', video_url):
 | 
				
			||||||
 | 
					                base_path_data = path_data.get(ext, path_data.get('default', {}))
 | 
				
			||||||
 | 
					                media_src = base_path_data.get('media_src')
 | 
				
			||||||
 | 
					                if not media_src:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                video_url = media_src + video_url
 | 
				
			||||||
 | 
					            if video_url in urls:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            urls.append(video_url)
 | 
				
			||||||
 | 
					            format_id = video_file.attrib['bitrate']
 | 
				
			||||||
 | 
					            if ext == 'smil':
 | 
				
			||||||
 | 
					                formats.extend(self._extract_smil_formats(video_url, video_id, fatal=False))
 | 
				
			||||||
 | 
					            elif ext == 'm3u8':
 | 
				
			||||||
 | 
					                formats.extend(self._extract_m3u8_formats(
 | 
				
			||||||
 | 
					                    video_url, video_id, 'mp4', m3u8_id=format_id, fatal=False))
 | 
				
			||||||
 | 
					            elif ext == 'f4m':
 | 
				
			||||||
 | 
					                formats.extend(self._extract_f4m_formats(
 | 
				
			||||||
 | 
					                    update_url_query(video_url, {'hdcore': '3.7.0'}),
 | 
				
			||||||
 | 
					                    video_id, f4m_id=format_id, fatal=False))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                f = {
 | 
				
			||||||
 | 
					                    'format_id': format_id,
 | 
				
			||||||
 | 
					                    'url': video_url,
 | 
				
			||||||
 | 
					                    'ext': ext,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                mobj = rex.search(format_id + video_url)
 | 
				
			||||||
 | 
					                if mobj:
 | 
				
			||||||
 | 
					                    f.update({
 | 
				
			||||||
 | 
					                        'width': int(mobj.group('width')),
 | 
				
			||||||
 | 
					                        'height': int(mobj.group('height')),
 | 
				
			||||||
 | 
					                        'tbr': int_or_none(mobj.group('bitrate')),
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                elif format_id.isdigit():
 | 
				
			||||||
 | 
					                    f['tbr'] = int(format_id)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    mobj = re.match(r'ios_(audio|[0-9]+)$', format_id)
 | 
				
			||||||
 | 
					                    if mobj:
 | 
				
			||||||
 | 
					                        if mobj.group(1) == 'audio':
 | 
				
			||||||
 | 
					                            f.update({
 | 
				
			||||||
 | 
					                                'vcodec': 'none',
 | 
				
			||||||
 | 
					                                'ext': 'm4a',
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            f['tbr'] = int(mobj.group(1))
 | 
				
			||||||
 | 
					                formats.append(f)
 | 
				
			||||||
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        subtitles = {}
 | 
				
			||||||
 | 
					        for source in video_data.findall('closedCaptions/source'):
 | 
				
			||||||
 | 
					            for track in source.findall('track'):
 | 
				
			||||||
 | 
					                source_url = source.get('url')
 | 
				
			||||||
 | 
					                if not source_url:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                subtitles.set_default(source.get('lang') or source.get('label') or 'en', []).append({
 | 
				
			||||||
 | 
					                    'url': source_url,
 | 
				
			||||||
 | 
					                    'ext': {
 | 
				
			||||||
 | 
					                        'scc': 'scc',
 | 
				
			||||||
 | 
					                        'webvtt': 'vtt',
 | 
				
			||||||
 | 
					                        'smptett': 'tt',
 | 
				
			||||||
 | 
					                    }.get(source.get('format'))
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        thumbnails = [{
 | 
				
			||||||
 | 
					            'id': image.get('cut'),
 | 
				
			||||||
 | 
					            'url': image.text,
 | 
				
			||||||
 | 
					            'width': int_or_none(image.get('width')),
 | 
				
			||||||
 | 
					            'height': int_or_none(image.get('height')),
 | 
				
			||||||
 | 
					        } for image in video_data.findall('images/image')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timestamp = None
 | 
				
			||||||
 | 
					        if 'cnn.com' not in data_src:
 | 
				
			||||||
 | 
					            timestamp = int_or_none(xpath_attr(video_data, 'dateCreated', 'uts'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
 | 
					            'thumbnails': thumbnails,
 | 
				
			||||||
 | 
					            'description': xpath_text(video_data, 'description'),
 | 
				
			||||||
 | 
					            'duration': parse_duration(xpath_text(video_data, 'length') or xpath_text(video_data, 'trt')),
 | 
				
			||||||
 | 
					            'timestamp': timestamp,
 | 
				
			||||||
 | 
					            'upload_date': xpath_attr(video_data, 'metas', 'version'),
 | 
				
			||||||
 | 
					            'series': xpath_text(video_data, 'showTitle'),
 | 
				
			||||||
 | 
					            'season_number': int_or_none(xpath_text(video_data, 'seasonNumber')),
 | 
				
			||||||
 | 
					            'episode_number': int_or_none(xpath_text(video_data, 'episodeNumber')),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
		Reference in New Issue
	
	Block a user