mirror of
				https://gitlab.com/ytdl-org/youtube-dl.git
				synced 2025-11-04 05:37:07 -05:00 
			
		
		
		
	[extractor/common] Extract f4m and m3u8 formats, subtitles and info
This commit is contained in:
		@@ -18,6 +18,7 @@ from ..compat import (
 | 
				
			|||||||
    compat_HTTPError,
 | 
					    compat_HTTPError,
 | 
				
			||||||
    compat_http_client,
 | 
					    compat_http_client,
 | 
				
			||||||
    compat_urllib_error,
 | 
					    compat_urllib_error,
 | 
				
			||||||
 | 
					    compat_urllib_parse,
 | 
				
			||||||
    compat_urllib_parse_urlparse,
 | 
					    compat_urllib_parse_urlparse,
 | 
				
			||||||
    compat_urllib_request,
 | 
					    compat_urllib_request,
 | 
				
			||||||
    compat_urlparse,
 | 
					    compat_urlparse,
 | 
				
			||||||
@@ -37,6 +38,7 @@ from ..utils import (
 | 
				
			|||||||
    RegexNotFoundError,
 | 
					    RegexNotFoundError,
 | 
				
			||||||
    sanitize_filename,
 | 
					    sanitize_filename,
 | 
				
			||||||
    unescapeHTML,
 | 
					    unescapeHTML,
 | 
				
			||||||
 | 
					    url_basename,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -978,69 +980,165 @@ class InfoExtractor(object):
 | 
				
			|||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: improve extraction
 | 
					    @staticmethod
 | 
				
			||||||
    def _extract_smil_formats(self, smil_url, video_id, fatal=True):
 | 
					    def _xpath_ns(path, namespace=None):
 | 
				
			||||||
        smil = self._download_xml(
 | 
					        if not namespace:
 | 
				
			||||||
            smil_url, video_id, 'Downloading SMIL file',
 | 
					            return path
 | 
				
			||||||
            'Unable to download SMIL file', fatal=fatal)
 | 
					        out = []
 | 
				
			||||||
 | 
					        for c in path.split('/'):
 | 
				
			||||||
 | 
					            if not c or c == '.':
 | 
				
			||||||
 | 
					                out.append(c)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                out.append('{%s}%s' % (namespace, c))
 | 
				
			||||||
 | 
					        return '/'.join(out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _extract_smil_formats(self, smil_url, video_id, fatal=True, f4m_params=None):
 | 
				
			||||||
 | 
					        smil = self._download_smil(smil_url, video_id, fatal=fatal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if smil is False:
 | 
					        if smil is False:
 | 
				
			||||||
            assert not fatal
 | 
					            assert not fatal
 | 
				
			||||||
            return []
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        base = smil.find('./head/meta').get('base')
 | 
					        namespace = self._search_regex(
 | 
				
			||||||
 | 
					            r'{([^}]+)?}smil', smil.tag, 'namespace', default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self._parse_smil_formats(
 | 
				
			||||||
 | 
					            smil, smil_url, video_id, namespace=namespace, f4m_params=f4m_params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _extract_smil_info(self, smil_url, video_id, fatal=True, f4m_params=None):
 | 
				
			||||||
 | 
					        smil = self._download_smil(smil_url, video_id, fatal=fatal)
 | 
				
			||||||
 | 
					        if smil is False:
 | 
				
			||||||
 | 
					            return {}
 | 
				
			||||||
 | 
					        return self._parse_smil(smil, smil_url, video_id, f4m_params=f4m_params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _download_smil(self, smil_url, video_id, fatal=True):
 | 
				
			||||||
 | 
					        return self._download_xml(
 | 
				
			||||||
 | 
					            smil_url, video_id, 'Downloading SMIL file',
 | 
				
			||||||
 | 
					            'Unable to download SMIL file', fatal=fatal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_smil(self, smil, smil_url, video_id, f4m_params=None):
 | 
				
			||||||
 | 
					        namespace = self._search_regex(
 | 
				
			||||||
 | 
					            r'{([^}]+)?}smil', smil.tag, 'namespace', default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formats = self._parse_smil_formats(
 | 
				
			||||||
 | 
					            smil, smil_url, video_id, namespace=namespace, f4m_params=f4m_params)
 | 
				
			||||||
 | 
					        subtitles = self._parse_smil_subtitles(smil, namespace=namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        video_id = os.path.splitext(url_basename(smil_url))[0]
 | 
				
			||||||
 | 
					        title = None
 | 
				
			||||||
 | 
					        description = None
 | 
				
			||||||
 | 
					        for meta in smil.findall(self._xpath_ns('./head/meta', namespace)):
 | 
				
			||||||
 | 
					            name = meta.attrib.get('name')
 | 
				
			||||||
 | 
					            content = meta.attrib.get('content')
 | 
				
			||||||
 | 
					            if not name or not content:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if not title and name == 'title':
 | 
				
			||||||
 | 
					                title = content
 | 
				
			||||||
 | 
					            elif not description and name in ('description', 'abstract'):
 | 
				
			||||||
 | 
					                description = content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'title': title or video_id,
 | 
				
			||||||
 | 
					            'description': description,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None):
 | 
				
			||||||
 | 
					        base = smil_url
 | 
				
			||||||
 | 
					        for meta in smil.findall(self._xpath_ns('./head/meta', namespace)):
 | 
				
			||||||
 | 
					            b = meta.get('base') or meta.get('httpBase')
 | 
				
			||||||
 | 
					            if b:
 | 
				
			||||||
 | 
					                base = b
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        rtmp_count = 0
 | 
					        rtmp_count = 0
 | 
				
			||||||
        if smil.findall('./body/seq/video'):
 | 
					        http_count = 0
 | 
				
			||||||
            video = smil.findall('./body/seq/video')[0]
 | 
					
 | 
				
			||||||
            fmts, rtmp_count = self._parse_smil_video(video, video_id, base, rtmp_count)
 | 
					        videos = smil.findall(self._xpath_ns('.//video', namespace))
 | 
				
			||||||
            formats.extend(fmts)
 | 
					        for video in videos:
 | 
				
			||||||
        else:
 | 
					            src = video.get('src')
 | 
				
			||||||
            for video in smil.findall('./body/switch/video'):
 | 
					            if not src:
 | 
				
			||||||
                fmts, rtmp_count = self._parse_smil_video(video, video_id, base, rtmp_count)
 | 
					                continue
 | 
				
			||||||
                formats.extend(fmts)
 | 
					
 | 
				
			||||||
 | 
					            bitrate = int_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
 | 
				
			||||||
 | 
					            filesize = int_or_none(video.get('size') or video.get('fileSize'))
 | 
				
			||||||
 | 
					            width = int_or_none(video.get('width'))
 | 
				
			||||||
 | 
					            height = int_or_none(video.get('height'))
 | 
				
			||||||
 | 
					            proto = video.get('proto')
 | 
				
			||||||
 | 
					            ext = video.get('ext')
 | 
				
			||||||
 | 
					            src_ext = determine_ext(src)
 | 
				
			||||||
 | 
					            streamer = video.get('streamer') or base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if proto == 'rtmp' or streamer.startswith('rtmp'):
 | 
				
			||||||
 | 
					                rtmp_count += 1
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'url': streamer,
 | 
				
			||||||
 | 
					                    'play_path': src,
 | 
				
			||||||
 | 
					                    'ext': 'flv',
 | 
				
			||||||
 | 
					                    'format_id': 'rtmp-%d' % (rtmp_count if bitrate is None else bitrate),
 | 
				
			||||||
 | 
					                    'tbr': bitrate,
 | 
				
			||||||
 | 
					                    'filesize': filesize,
 | 
				
			||||||
 | 
					                    'width': width,
 | 
				
			||||||
 | 
					                    'height': height,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            src_url = src if src.startswith('http') else compat_urlparse.urljoin(base, src)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if proto == 'm3u8' or src_ext == 'm3u8':
 | 
				
			||||||
 | 
					                formats.extend(self._extract_m3u8_formats(
 | 
				
			||||||
 | 
					                    src_url, video_id, ext or 'mp4', m3u8_id='hls'))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if src_ext == 'f4m':
 | 
				
			||||||
 | 
					                f4m_url = src_url
 | 
				
			||||||
 | 
					                if not f4m_params:
 | 
				
			||||||
 | 
					                    f4m_params = {
 | 
				
			||||||
 | 
					                        'hdcore': '3.2.0',
 | 
				
			||||||
 | 
					                        'plugin': 'flowplayer-3.2.0.1',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                f4m_url += '&' if '?' in f4m_url else '?'
 | 
				
			||||||
 | 
					                f4m_url += compat_urllib_parse.urlencode(f4m_params).encode('utf-8')
 | 
				
			||||||
 | 
					                formats.extend(self._extract_f4m_formats(f4m_url, video_id, f4m_id='hds'))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if src_url.startswith('http'):
 | 
				
			||||||
 | 
					                http_count += 1
 | 
				
			||||||
 | 
					                formats.append({
 | 
				
			||||||
 | 
					                    'url': src_url,
 | 
				
			||||||
 | 
					                    'ext': ext or src_ext or 'flv',
 | 
				
			||||||
 | 
					                    'format_id': 'http-%d' % (bitrate or http_count),
 | 
				
			||||||
 | 
					                    'tbr': bitrate,
 | 
				
			||||||
 | 
					                    'filesize': filesize,
 | 
				
			||||||
 | 
					                    'width': width,
 | 
				
			||||||
 | 
					                    'height': height,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sort_formats(formats)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return formats
 | 
					        return formats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _parse_smil_video(self, video, video_id, base, rtmp_count):
 | 
					    def _parse_smil_subtitles(self, smil, namespace=None):
 | 
				
			||||||
        src = video.get('src')
 | 
					        subtitles = {}
 | 
				
			||||||
        if not src:
 | 
					        for num, textstream in enumerate(smil.findall(self._xpath_ns('.//textstream', namespace))):
 | 
				
			||||||
            return [], rtmp_count
 | 
					            src = textstream.get('src')
 | 
				
			||||||
        bitrate = int_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
 | 
					            if not src:
 | 
				
			||||||
        width = int_or_none(video.get('width'))
 | 
					                continue
 | 
				
			||||||
        height = int_or_none(video.get('height'))
 | 
					            ext = textstream.get('ext') or determine_ext(src)
 | 
				
			||||||
        proto = video.get('proto')
 | 
					            if not ext:
 | 
				
			||||||
        if not proto:
 | 
					                type_ = textstream.get('type')
 | 
				
			||||||
            if base:
 | 
					                if type_ == 'text/srt':
 | 
				
			||||||
                if base.startswith('rtmp'):
 | 
					                    ext = 'srt'
 | 
				
			||||||
                    proto = 'rtmp'
 | 
					            lang = textstream.get('systemLanguage') or textstream.get('systemLanguageName')
 | 
				
			||||||
                elif base.startswith('http'):
 | 
					            subtitles.setdefault(lang, []).append({
 | 
				
			||||||
                    proto = 'http'
 | 
					                'url': src,
 | 
				
			||||||
        ext = video.get('ext')
 | 
					                'ext': ext,
 | 
				
			||||||
        if proto == 'm3u8':
 | 
					            })
 | 
				
			||||||
            return self._extract_m3u8_formats(src, video_id, ext), rtmp_count
 | 
					        return subtitles
 | 
				
			||||||
        elif proto == 'rtmp':
 | 
					 | 
				
			||||||
            rtmp_count += 1
 | 
					 | 
				
			||||||
            streamer = video.get('streamer') or base
 | 
					 | 
				
			||||||
            return ([{
 | 
					 | 
				
			||||||
                'url': streamer,
 | 
					 | 
				
			||||||
                'play_path': src,
 | 
					 | 
				
			||||||
                'ext': 'flv',
 | 
					 | 
				
			||||||
                'format_id': 'rtmp-%d' % (rtmp_count if bitrate is None else bitrate),
 | 
					 | 
				
			||||||
                'tbr': bitrate,
 | 
					 | 
				
			||||||
                'width': width,
 | 
					 | 
				
			||||||
                'height': height,
 | 
					 | 
				
			||||||
            }], rtmp_count)
 | 
					 | 
				
			||||||
        elif proto.startswith('http'):
 | 
					 | 
				
			||||||
            return ([{
 | 
					 | 
				
			||||||
                'url': base + src,
 | 
					 | 
				
			||||||
                'ext': ext or 'flv',
 | 
					 | 
				
			||||||
                'tbr': bitrate,
 | 
					 | 
				
			||||||
                'width': width,
 | 
					 | 
				
			||||||
                'height': height,
 | 
					 | 
				
			||||||
            }], rtmp_count)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _live_title(self, name):
 | 
					    def _live_title(self, name):
 | 
				
			||||||
        """ Generate the title for a live video """
 | 
					        """ Generate the title for a live video """
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user