summaryrefslogtreecommitdiff
path: root/youtube_dl/extractor/ceskatelevize.py
blob: fe677d8e83f0f8b2eb20aa34acebc6ecc06fada2 (plain)
    1 # coding: utf-8
    2 from __future__ import unicode_literals
    3 
    4 import re
    5 
    6 from .common import InfoExtractor
    7 from ..compat import (
    8     compat_urllib_parse_unquote,
    9     compat_urllib_parse_urlparse,
   10 )
   11 from ..utils import (
   12     ExtractorError,
   13     float_or_none,
   14     sanitized_Request,
   15     str_or_none,
   16     traverse_obj,
   17     urlencode_postdata,
   18     USER_AGENTS,
   19 )
   20 
   21 
   22 class CeskaTelevizeIE(InfoExtractor):
   23     _VALID_URL = r'https?://(?:www\.)?ceskatelevize\.cz/(?:ivysilani|porady|zive)/(?:[^/?#&]+/)*(?P<id>[^/#?]+)'
   24     _TESTS = [{
   25         'url': 'http://www.ceskatelevize.cz/ivysilani/10441294653-hyde-park-civilizace/215411058090502/bonus/20641-bonus-01-en',
   26         'info_dict': {
   27             'id': '61924494877028507',
   28             'ext': 'mp4',
   29             'title': 'Bonus 01 - En - Hyde Park Civilizace',
   30             'description': 'English Subtittles',
   31             'thumbnail': r're:^https?://.*\.jpg',
   32             'duration': 81.3,
   33         },
   34         'params': {
   35             # m3u8 download
   36             'skip_download': True,
   37         },
   38     }, {
   39         # live stream
   40         'url': 'http://www.ceskatelevize.cz/zive/ct1/',
   41         'info_dict': {
   42             'id': '102',
   43             'ext': 'mp4',
   44             'title': r'ČT1 - živé vysílání online',
   45             'description': 'Sledujte živé vysílání kanálu ČT1 online. Vybírat si můžete i z dalších kanálů České televize na kterémkoli z vašich zařízení.',
   46             'is_live': True,
   47         },
   48         'params': {
   49             # m3u8 download
   50             'skip_download': True,
   51         },
   52     }, {
   53         # another
   54         'url': 'http://www.ceskatelevize.cz/ivysilani/zive/ct4/',
   55         'only_matching': True,
   56         'info_dict': {
   57             'id': 402,
   58             'ext': 'mp4',
   59             'title': r're:^ČT Sport \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
   60             'is_live': True,
   61         },
   62         # 'skip': 'Georestricted to Czech Republic',
   63     }, {
   64         'url': 'http://www.ceskatelevize.cz/ivysilani/embed/iFramePlayer.php?hash=d6a3e1370d2e4fa76296b90bad4dfc19673b641e&IDEC=217 562 22150/0004&channelID=1&width=100%25',
   65         'only_matching': True,
   66     }, {
   67         # video with 18+ caution trailer
   68         'url': 'http://www.ceskatelevize.cz/porady/10520528904-queer/215562210900007-bogotart/',
   69         'info_dict': {
   70             'id': '215562210900007-bogotart',
   71             'title': 'Bogotart - Queer',
   72             'description': 'Hlavní město Kolumbie v doprovodu queer umělců. Vroucí svět plný vášně, sebevědomí, ale i násilí a bolesti',
   73         },
   74         'playlist': [{
   75             'info_dict': {
   76                 'id': '61924494877311053',
   77                 'ext': 'mp4',
   78                 'title': 'Bogotart - Queer (Varování 18+)',
   79                 'duration': 11.9,
   80             },
   81         }, {
   82             'info_dict': {
   83                 'id': '61924494877068022',
   84                 'ext': 'mp4',
   85                 'title': 'Bogotart - Queer (Queer)',
   86                 'thumbnail': r're:^https?://.*\.jpg',
   87                 'duration': 1558.3,
   88             },
   89         }],
   90         'params': {
   91             # m3u8 download
   92             'skip_download': True,
   93         },
   94     }, {
   95         # iframe embed
   96         'url': 'http://www.ceskatelevize.cz/porady/10614999031-neviditelni/21251212048/',
   97         'only_matching': True,
   98     }]
   99 
  100     def _search_nextjs_data(self, webpage, video_id, **kw):
  101         return self._parse_json(
  102             self._search_regex(
  103                 r'(?s)<script[^>]+id=[\'"]__NEXT_DATA__[\'"][^>]*>([^<]+)</script>',
  104                 webpage, 'next.js data', **kw),
  105             video_id, **kw)
  106 
  107     def _real_extract(self, url):
  108         playlist_id = self._match_id(url)
  109         webpage, urlh = self._download_webpage_handle(url, playlist_id)
  110         parsed_url = compat_urllib_parse_urlparse(urlh.geturl())
  111         site_name = self._og_search_property('site_name', webpage, fatal=False, default='Česká televize')
  112         playlist_title = self._og_search_title(webpage, default=None)
  113         if site_name and playlist_title:
  114             playlist_title = re.split(r'\s*[—|]\s*%s' % (site_name, ), playlist_title, 1)[0]
  115         playlist_description = self._og_search_description(webpage, default=None)
  116         if playlist_description:
  117             playlist_description = playlist_description.replace('\xa0', ' ')
  118 
  119         type_ = 'IDEC'
  120         if re.search(r'(^/porady|/zive)/', parsed_url.path):
  121             next_data = self._search_nextjs_data(webpage, playlist_id)
  122             if '/zive/' in parsed_url.path:
  123                 idec = traverse_obj(next_data, ('props', 'pageProps', 'data', 'liveBroadcast', 'current', 'idec'), get_all=False)
  124             else:
  125                 idec = traverse_obj(next_data, ('props', 'pageProps', 'data', ('show', 'mediaMeta'), 'idec'), get_all=False)
  126                 if not idec:
  127                     idec = traverse_obj(next_data, ('props', 'pageProps', 'data', 'videobonusDetail', 'bonusId'), get_all=False)
  128                     if idec:
  129                         type_ = 'bonus'
  130             if not idec:
  131                 raise ExtractorError('Failed to find IDEC id')
  132             iframe_hash = self._download_webpage(
  133                 'https://www.ceskatelevize.cz/v-api/iframe-hash/',
  134                 playlist_id, note='Getting IFRAME hash')
  135             query = {'hash': iframe_hash, 'origin': 'iVysilani', 'autoStart': 'true', type_: idec, }
  136             webpage = self._download_webpage(
  137                 'https://www.ceskatelevize.cz/ivysilani/embed/iFramePlayer.php',
  138                 playlist_id, note='Downloading player', query=query)
  139 
  140         NOT_AVAILABLE_STRING = 'This content is not available at your territory due to limited copyright.'
  141         if '%s</p>' % NOT_AVAILABLE_STRING in webpage:
  142             self.raise_geo_restricted(NOT_AVAILABLE_STRING)
  143         if any(not_found in webpage for not_found in ('Neplatný parametr pro videopřehrávač', 'IDEC nebyl nalezen', )):
  144             raise ExtractorError('no video with IDEC available', video_id=idec, expected=True)
  145 
  146         type_ = None
  147         episode_id = None
  148 
  149         playlist = self._parse_json(
  150             self._search_regex(
  151                 r'getPlaylistUrl\(\[({.+?})\]', webpage, 'playlist',
  152                 default='{}'), playlist_id)
  153         if playlist:
  154             type_ = playlist.get('type')
  155             episode_id = playlist.get('id')
  156 
  157         if not type_:
  158             type_ = self._html_search_regex(
  159                 r'getPlaylistUrl\(\[\{"type":"(.+?)","id":".+?"\}\],',
  160                 webpage, 'type')
  161         if not episode_id:
  162             episode_id = self._html_search_regex(
  163                 r'getPlaylistUrl\(\[\{"type":".+?","id":"(.+?)"\}\],',
  164                 webpage, 'episode_id')
  165 
  166         data = {
  167             'playlist[0][type]': type_,
  168             'playlist[0][id]': episode_id,
  169             'requestUrl': parsed_url.path,
  170             'requestSource': 'iVysilani',
  171         }
  172 
  173         entries = []
  174 
  175         for user_agent in (None, USER_AGENTS['Safari']):
  176             req = sanitized_Request(
  177                 'https://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist/',
  178                 data=urlencode_postdata(data))
  179 
  180             req.add_header('Content-type', 'application/x-www-form-urlencoded')
  181             req.add_header('x-addr', '127.0.0.1')
  182             req.add_header('X-Requested-With', 'XMLHttpRequest')
  183             if user_agent:
  184                 req.add_header('User-Agent', user_agent)
  185             req.add_header('Referer', url)
  186 
  187             playlistpage = self._download_json(req, playlist_id, fatal=False)
  188 
  189             if not playlistpage:
  190                 continue
  191 
  192             playlist_url = playlistpage['url']
  193             if playlist_url == 'error_region':
  194                 raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
  195 
  196             req = sanitized_Request(compat_urllib_parse_unquote(playlist_url))
  197             req.add_header('Referer', url)
  198 
  199             playlist = self._download_json(req, playlist_id, fatal=False)
  200             if not playlist:
  201                 continue
  202 
  203             playlist = playlist.get('playlist')
  204             if not isinstance(playlist, list):
  205                 continue
  206 
  207             playlist_len = len(playlist)
  208 
  209             for num, item in enumerate(playlist):
  210                 is_live = item.get('type') == 'LIVE'
  211                 formats = []
  212                 for format_id, stream_url in item.get('streamUrls', {}).items():
  213                     if 'drmOnly=true' in stream_url:
  214                         continue
  215                     if 'playerType=flash' in stream_url:
  216                         stream_formats = self._extract_m3u8_formats(
  217                             stream_url, playlist_id, 'mp4', 'm3u8_native',
  218                             m3u8_id='hls-%s' % format_id, fatal=False)
  219                     else:
  220                         stream_formats = self._extract_mpd_formats(
  221                             stream_url, playlist_id,
  222                             mpd_id='dash-%s' % format_id, fatal=False)
  223                     # See https://github.com/ytdl-org/youtube-dl/issues/12119#issuecomment-280037031
  224                     if format_id == 'audioDescription':
  225                         for f in stream_formats:
  226                             f['source_preference'] = -10
  227                     formats.extend(stream_formats)
  228 
  229                 if user_agent and len(entries) == playlist_len:
  230                     entries[num]['formats'].extend(formats)
  231                     continue
  232 
  233                 item_id = str_or_none(item.get('id') or item['assetId'])
  234                 title = item['title']
  235 
  236                 duration = float_or_none(item.get('duration'))
  237                 thumbnail = item.get('previewImageUrl')
  238 
  239                 subtitles = {}
  240                 if item.get('type') == 'VOD':
  241                     subs = item.get('subtitles')
  242                     if subs:
  243                         subtitles = self.extract_subtitles(episode_id, subs)
  244 
  245                 if playlist_len == 1:
  246                     final_title = playlist_title or title
  247                 else:
  248                     final_title = '%s (%s)' % (playlist_title, title)
  249 
  250                 entries.append({
  251                     'id': item_id,
  252                     'title': final_title,
  253                     'description': playlist_description if playlist_len == 1 else None,
  254                     'thumbnail': thumbnail,
  255                     'duration': duration,
  256                     'formats': formats,
  257                     'subtitles': subtitles,
  258                     'is_live': is_live,
  259                 })
  260 
  261         for e in entries:
  262             self._sort_formats(e['formats'])
  263 
  264         if len(entries) == 1:
  265             return entries[0]
  266         return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
  267 
  268     def _get_subtitles(self, episode_id, subs):
  269         original_subtitles = self._download_webpage(
  270             subs[0]['url'], episode_id, 'Downloading subtitles')
  271         srt_subs = self._fix_subtitles(original_subtitles)
  272         return {
  273             'cs': [{
  274                 'ext': 'srt',
  275                 'data': srt_subs,
  276             }]
  277         }
  278 
  279     @staticmethod
  280     def _fix_subtitles(subtitles):
  281         """ Convert millisecond-based subtitles to SRT """
  282 
  283         def _msectotimecode(msec):
  284             """ Helper utility to convert milliseconds to timecode """
  285             components = []
  286             for divider in [1000, 60, 60, 100]:
  287                 components.append(msec % divider)
  288                 msec //= divider
  289             return '{3:02}:{2:02}:{1:02},{0:03}'.format(*components)
  290 
  291         def _fix_subtitle(subtitle):
  292             for line in subtitle.splitlines():
  293                 m = re.match(r'^\s*([0-9]+);\s*([0-9]+)\s+([0-9]+)\s*$', line)
  294                 if m:
  295                     yield m.group(1)
  296                     start, stop = (_msectotimecode(int(t)) for t in m.groups()[1:])
  297                     yield '{0} --> {1}'.format(start, stop)
  298                 else:
  299                     yield line
  300 
  301         return '\r\n'.join(_fix_subtitle(subtitles))

Generated by cgit