aboutsummaryrefslogtreecommitdiff
path: root/youtube_dl/extractor/ceskatelevize.py
diff options
context:
space:
mode:
Diffstat (limited to 'youtube_dl/extractor/ceskatelevize.py')
-rw-r--r--youtube_dl/extractor/ceskatelevize.py278
1 files changed, 181 insertions, 97 deletions
diff --git a/youtube_dl/extractor/ceskatelevize.py b/youtube_dl/extractor/ceskatelevize.py
index 5a58d1777..fe677d8e8 100644
--- a/youtube_dl/extractor/ceskatelevize.py
+++ b/youtube_dl/extractor/ceskatelevize.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# coding: utf-8
from __future__ import unicode_literals
import re
@@ -12,75 +12,78 @@ from ..utils import (
ExtractorError,
float_or_none,
sanitized_Request,
+ str_or_none,
+ traverse_obj,
urlencode_postdata,
+ USER_AGENTS,
)
class CeskaTelevizeIE(InfoExtractor):
- _VALID_URL = r'https?://www\.ceskatelevize\.cz/(porady|ivysilani)/(?:[^/]+/)*(?P<id>[^/#?]+)/*(?:[#?].*)?$'
+ _VALID_URL = r'https?://(?:www\.)?ceskatelevize\.cz/(?:ivysilani|porady|zive)/(?:[^/?#&]+/)*(?P<id>[^/#?]+)'
_TESTS = [{
- 'url': 'http://www.ceskatelevize.cz/ivysilani/ivysilani/10441294653-hyde-park-civilizace/214411058091220',
+ 'url': 'http://www.ceskatelevize.cz/ivysilani/10441294653-hyde-park-civilizace/215411058090502/bonus/20641-bonus-01-en',
'info_dict': {
- 'id': '61924494876951776',
+ 'id': '61924494877028507',
'ext': 'mp4',
- 'title': 'Hyde Park Civilizace',
- 'description': 'md5:fe93f6eda372d150759d11644ebbfb4a',
- 'thumbnail': 're:^https?://.*\.jpg',
- 'duration': 3350,
+ 'title': 'Bonus 01 - En - Hyde Park Civilizace',
+ 'description': 'English Subtittles',
+ 'thumbnail': r're:^https?://.*\.jpg',
+ 'duration': 81.3,
},
'params': {
# m3u8 download
'skip_download': True,
},
}, {
- 'url': 'http://www.ceskatelevize.cz/ivysilani/10441294653-hyde-park-civilizace/215411058090502/bonus/20641-bonus-01-en',
+ # live stream
+ 'url': 'http://www.ceskatelevize.cz/zive/ct1/',
'info_dict': {
- 'id': '61924494877028507',
+ 'id': '102',
'ext': 'mp4',
- 'title': 'Hyde Park Civilizace: Bonus 01 - En',
- 'description': 'English Subtittles',
- 'thumbnail': 're:^https?://.*\.jpg',
- 'duration': 81.3,
+ 'title': r'ČT1 - živé vysílání online',
+ '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í.',
+ 'is_live': True,
},
'params': {
# m3u8 download
'skip_download': True,
},
}, {
- # live stream
+ # another
'url': 'http://www.ceskatelevize.cz/ivysilani/zive/ct4/',
+ 'only_matching': True,
'info_dict': {
'id': 402,
'ext': 'mp4',
- 'title': 're:^ČT Sport \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
+ 'title': r're:^ČT Sport \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
'is_live': True,
},
- 'params': {
- # m3u8 download
- 'skip_download': True,
- },
- 'skip': 'Georestricted to Czech Republic',
+ # 'skip': 'Georestricted to Czech Republic',
+ }, {
+ 'url': 'http://www.ceskatelevize.cz/ivysilani/embed/iFramePlayer.php?hash=d6a3e1370d2e4fa76296b90bad4dfc19673b641e&IDEC=217 562 22150/0004&channelID=1&width=100%25',
+ 'only_matching': True,
}, {
# video with 18+ caution trailer
'url': 'http://www.ceskatelevize.cz/porady/10520528904-queer/215562210900007-bogotart/',
'info_dict': {
'id': '215562210900007-bogotart',
- 'title': 'Queer: Bogotart',
- 'description': 'Alternativní průvodce současným queer světem',
+ 'title': 'Bogotart - Queer',
+ 'description': 'Hlavní město Kolumbie v doprovodu queer umělců. Vroucí svět plný vášně, sebevědomí, ale i násilí a bolesti',
},
'playlist': [{
'info_dict': {
- 'id': '61924494876844842',
+ 'id': '61924494877311053',
'ext': 'mp4',
- 'title': 'Queer: Bogotart (Varování 18+)',
- 'duration': 10.2,
+ 'title': 'Bogotart - Queer (Varování 18+)',
+ 'duration': 11.9,
},
}, {
'info_dict': {
'id': '61924494877068022',
'ext': 'mp4',
- 'title': 'Queer: Bogotart (Queer)',
- 'thumbnail': 're:^https?://.*\.jpg',
+ 'title': 'Bogotart - Queer (Queer)',
+ 'thumbnail': r're:^https?://.*\.jpg',
'duration': 1558.3,
},
}],
@@ -88,97 +91,178 @@ class CeskaTelevizeIE(InfoExtractor):
# m3u8 download
'skip_download': True,
},
+ }, {
+ # iframe embed
+ 'url': 'http://www.ceskatelevize.cz/porady/10614999031-neviditelni/21251212048/',
+ 'only_matching': True,
}]
- def _real_extract(self, url):
- url = url.replace('/porady/', '/ivysilani/').replace('/video/', '')
+ def _search_nextjs_data(self, webpage, video_id, **kw):
+ return self._parse_json(
+ self._search_regex(
+ r'(?s)<script[^>]+id=[\'"]__NEXT_DATA__[\'"][^>]*>([^<]+)</script>',
+ webpage, 'next.js data', **kw),
+ video_id, **kw)
- mobj = re.match(self._VALID_URL, url)
- playlist_id = mobj.group('id')
+ def _real_extract(self, url):
+ playlist_id = self._match_id(url)
+ webpage, urlh = self._download_webpage_handle(url, playlist_id)
+ parsed_url = compat_urllib_parse_urlparse(urlh.geturl())
+ site_name = self._og_search_property('site_name', webpage, fatal=False, default='Česká televize')
+ playlist_title = self._og_search_title(webpage, default=None)
+ if site_name and playlist_title:
+ playlist_title = re.split(r'\s*[—|]\s*%s' % (site_name, ), playlist_title, 1)[0]
+ playlist_description = self._og_search_description(webpage, default=None)
+ if playlist_description:
+ playlist_description = playlist_description.replace('\xa0', ' ')
- webpage = self._download_webpage(url, playlist_id)
+ type_ = 'IDEC'
+ if re.search(r'(^/porady|/zive)/', parsed_url.path):
+ next_data = self._search_nextjs_data(webpage, playlist_id)
+ if '/zive/' in parsed_url.path:
+ idec = traverse_obj(next_data, ('props', 'pageProps', 'data', 'liveBroadcast', 'current', 'idec'), get_all=False)
+ else:
+ idec = traverse_obj(next_data, ('props', 'pageProps', 'data', ('show', 'mediaMeta'), 'idec'), get_all=False)
+ if not idec:
+ idec = traverse_obj(next_data, ('props', 'pageProps', 'data', 'videobonusDetail', 'bonusId'), get_all=False)
+ if idec:
+ type_ = 'bonus'
+ if not idec:
+ raise ExtractorError('Failed to find IDEC id')
+ iframe_hash = self._download_webpage(
+ 'https://www.ceskatelevize.cz/v-api/iframe-hash/',
+ playlist_id, note='Getting IFRAME hash')
+ query = {'hash': iframe_hash, 'origin': 'iVysilani', 'autoStart': 'true', type_: idec, }
+ webpage = self._download_webpage(
+ 'https://www.ceskatelevize.cz/ivysilani/embed/iFramePlayer.php',
+ playlist_id, note='Downloading player', query=query)
NOT_AVAILABLE_STRING = 'This content is not available at your territory due to limited copyright.'
if '%s</p>' % NOT_AVAILABLE_STRING in webpage:
- raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
+ self.raise_geo_restricted(NOT_AVAILABLE_STRING)
+ if any(not_found in webpage for not_found in ('Neplatný parametr pro videopřehrávač', 'IDEC nebyl nalezen', )):
+ raise ExtractorError('no video with IDEC available', video_id=idec, expected=True)
+
+ type_ = None
+ episode_id = None
- typ = self._html_search_regex(
- r'getPlaylistUrl\(\[\{"type":"(.+?)","id":".+?"\}\],', webpage, 'type')
- episode_id = self._html_search_regex(
- r'getPlaylistUrl\(\[\{"type":".+?","id":"(.+?)"\}\],', webpage, 'episode_id')
+ playlist = self._parse_json(
+ self._search_regex(
+ r'getPlaylistUrl\(\[({.+?})\]', webpage, 'playlist',
+ default='{}'), playlist_id)
+ if playlist:
+ type_ = playlist.get('type')
+ episode_id = playlist.get('id')
+
+ if not type_:
+ type_ = self._html_search_regex(
+ r'getPlaylistUrl\(\[\{"type":"(.+?)","id":".+?"\}\],',
+ webpage, 'type')
+ if not episode_id:
+ episode_id = self._html_search_regex(
+ r'getPlaylistUrl\(\[\{"type":".+?","id":"(.+?)"\}\],',
+ webpage, 'episode_id')
data = {
- 'playlist[0][type]': typ,
+ 'playlist[0][type]': type_,
'playlist[0][id]': episode_id,
- 'requestUrl': compat_urllib_parse_urlparse(url).path,
+ 'requestUrl': parsed_url.path,
'requestSource': 'iVysilani',
}
- req = sanitized_Request(
- 'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
- data=urlencode_postdata(data))
+ entries = []
+
+ for user_agent in (None, USER_AGENTS['Safari']):
+ req = sanitized_Request(
+ 'https://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist/',
+ data=urlencode_postdata(data))
- req.add_header('Content-type', 'application/x-www-form-urlencoded')
- req.add_header('x-addr', '127.0.0.1')
- req.add_header('X-Requested-With', 'XMLHttpRequest')
- req.add_header('Referer', url)
+ req.add_header('Content-type', 'application/x-www-form-urlencoded')
+ req.add_header('x-addr', '127.0.0.1')
+ req.add_header('X-Requested-With', 'XMLHttpRequest')
+ if user_agent:
+ req.add_header('User-Agent', user_agent)
+ req.add_header('Referer', url)
- playlistpage = self._download_json(req, playlist_id)
+ playlistpage = self._download_json(req, playlist_id, fatal=False)
- playlist_url = playlistpage['url']
- if playlist_url == 'error_region':
- raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
+ if not playlistpage:
+ continue
- req = sanitized_Request(compat_urllib_parse_unquote(playlist_url))
- req.add_header('Referer', url)
+ playlist_url = playlistpage['url']
+ if playlist_url == 'error_region':
+ raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
- playlist_title = self._og_search_title(webpage, default=None)
- playlist_description = self._og_search_description(webpage, default=None)
+ req = sanitized_Request(compat_urllib_parse_unquote(playlist_url))
+ req.add_header('Referer', url)
- playlist = self._download_json(req, playlist_id)['playlist']
- playlist_len = len(playlist)
+ playlist = self._download_json(req, playlist_id, fatal=False)
+ if not playlist:
+ continue
- entries = []
- for item in playlist:
- is_live = item.get('type') == 'LIVE'
- formats = []
- for format_id, stream_url in item['streamUrls'].items():
- formats.extend(self._extract_m3u8_formats(
- stream_url, playlist_id, 'mp4',
- entry_protocol='m3u8' if is_live else 'm3u8_native',
- fatal=False))
- self._sort_formats(formats)
-
- item_id = item.get('id') or item['assetId']
- title = item['title']
-
- duration = float_or_none(item.get('duration'))
- thumbnail = item.get('previewImageUrl')
-
- subtitles = {}
- if item.get('type') == 'VOD':
- subs = item.get('subtitles')
- if subs:
- subtitles = self.extract_subtitles(episode_id, subs)
-
- if playlist_len == 1:
- final_title = playlist_title or title
- if is_live:
- final_title = self._live_title(final_title)
- else:
- final_title = '%s (%s)' % (playlist_title, title)
-
- entries.append({
- 'id': item_id,
- 'title': final_title,
- 'description': playlist_description if playlist_len == 1 else None,
- 'thumbnail': thumbnail,
- 'duration': duration,
- 'formats': formats,
- 'subtitles': subtitles,
- 'is_live': is_live,
- })
+ playlist = playlist.get('playlist')
+ if not isinstance(playlist, list):
+ continue
+
+ playlist_len = len(playlist)
+
+ for num, item in enumerate(playlist):
+ is_live = item.get('type') == 'LIVE'
+ formats = []
+ for format_id, stream_url in item.get('streamUrls', {}).items():
+ if 'drmOnly=true' in stream_url:
+ continue
+ if 'playerType=flash' in stream_url:
+ stream_formats = self._extract_m3u8_formats(
+ stream_url, playlist_id, 'mp4', 'm3u8_native',
+ m3u8_id='hls-%s' % format_id, fatal=False)
+ else:
+ stream_formats = self._extract_mpd_formats(
+ stream_url, playlist_id,
+ mpd_id='dash-%s' % format_id, fatal=False)
+ # See https://github.com/ytdl-org/youtube-dl/issues/12119#issuecomment-280037031
+ if format_id == 'audioDescription':
+ for f in stream_formats:
+ f['source_preference'] = -10
+ formats.extend(stream_formats)
+
+ if user_agent and len(entries) == playlist_len:
+ entries[num]['formats'].extend(formats)
+ continue
+
+ item_id = str_or_none(item.get('id') or item['assetId'])
+ title = item['title']
+
+ duration = float_or_none(item.get('duration'))
+ thumbnail = item.get('previewImageUrl')
+
+ subtitles = {}
+ if item.get('type') == 'VOD':
+ subs = item.get('subtitles')
+ if subs:
+ subtitles = self.extract_subtitles(episode_id, subs)
+
+ if playlist_len == 1:
+ final_title = playlist_title or title
+ else:
+ final_title = '%s (%s)' % (playlist_title, title)
+
+ entries.append({
+ 'id': item_id,
+ 'title': final_title,
+ 'description': playlist_description if playlist_len == 1 else None,
+ 'thumbnail': thumbnail,
+ 'duration': duration,
+ 'formats': formats,
+ 'subtitles': subtitles,
+ 'is_live': is_live,
+ })
+
+ for e in entries:
+ self._sort_formats(e['formats'])
+ if len(entries) == 1:
+ return entries[0]
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
def _get_subtitles(self, episode_id, subs):