2 from __future__ 
import unicode_literals
 
   6 from .common 
import InfoExtractor
 
   7 from ..compat 
import compat_str
 
  23 class ZDFBaseIE(InfoExtractor
): 
  24     def _call_api(self
, url
, player
, referrer
, video_id
, item
): 
  25         return self
._download
_json
( 
  26             url
, video_id
, 'Downloading JSON %s' % item
, 
  29                 'Api-Auth': 'Bearer %s' % player
['apiToken'], 
  32     def _extract_player(self
, webpage
, video_id
, fatal
=True): 
  33         return self
._parse
_json
( 
  35                 r
'(?s)data-zdfplayer-jsb=(["\'])(?P
<json
>{.+?
})\
1', webpage, 
  36                 'player JSON
', default='{}' if not fatal else NO_DEFAULT, 
  41 class ZDFIE(ZDFBaseIE): 
  42     _VALID_URL = r'https?
://www\
.zdf\
.de
/(?
:[^
/]+/)*(?P
<id>[^
/?
]+)\
.html
' 
  43     _QUALITIES = ('auto
', 'low
', 'med
', 'high
', 'veryhigh
') 
  46         'url
': 'https
://www
.zdf
.de
/dokumentation
/terra
-x
/die
-magie
-der
-farben
-von
-koenigspurpur
-und
-jeansblau
-100.html
', 
  48             'id': 'die
-magie
-der
-farben
-von
-koenigspurpur
-und
-jeansblau
-100', 
  50             'title
': 'Die Magie der 
Farben (2/2)', 
  51             'description
': 'md5
:a89da10c928c6235401066b60a6d5c1a
', 
  53             'timestamp
': 1465021200, 
  54             'upload_date
': '20160604', 
  57         'url
': 'https
://www
.zdf
.de
/service
-und
-hilfe
/die
-neue
-zdf
-mediathek
/zdfmediathek
-trailer
-100.html
', 
  58         'only_matching
': True, 
  60         'url
': 'https
://www
.zdf
.de
/filme
/taunuskrimi
/die
-lebenden
-und
-die
-toten
-1---ein
-taunuskrimi
-100.html
', 
  61         'only_matching
': True, 
  63         'url
': 'https
://www
.zdf
.de
/dokumentation
/planet
-e
/planet
-e
-uebersichtsseite
-weitere
-dokumentationen
-von
-planet
-e
-100.html
', 
  64         'only_matching
': True, 
  68     def _extract_subtitles(src): 
  70         for caption in try_get(src, lambda x: x['captions
'], list) or []: 
  71             subtitle_url = url_or_none(caption.get('uri
')) 
  73                 lang = caption.get('language
', 'deu
') 
  74                 subtitles.setdefault(lang, []).append({ 
  79     def _extract_format(self, video_id, formats, format_urls, meta): 
  80         format_url = url_or_none(meta.get('url
')) 
  83         if format_url in format_urls: 
  85         format_urls.add(format_url) 
  86         mime_type = meta.get('mimeType
') 
  87         ext = determine_ext(format_url) 
  88         if mime_type == 'application
/x
-mpegURL
' or ext == 'm3u8
': 
  89             formats.extend(self._extract_m3u8_formats( 
  90                 format_url, video_id, 'mp4
', m3u8_id='hls
', 
  91                 entry_protocol='m3u8_native
', fatal=False)) 
  92         elif mime_type == 'application
/f4m
+xml
' or ext == 'f4m
': 
  93             formats.extend(self._extract_f4m_formats( 
  94                 update_url_query(format_url, {'hdcore
': '3.7.0'}), video_id, f4m_id='hds
', fatal=False)) 
  96             f = parse_codecs(meta.get('mimeCodec
')) 
  98             for p in (meta.get('type'), meta.get('quality
')): 
  99                 if p and isinstance(p, compat_str): 
 103                 'format_id
': '-'.join(format_id), 
 104                 'format_note
': meta.get('quality
'), 
 105                 'language
': meta.get('language
'), 
 106                 'quality
': qualities(self._QUALITIES)(meta.get('quality
')), 
 111     def _extract_entry(self, url, player, content, video_id): 
 112         title = content.get('title
') or content['teaserHeadline
'] 
 114         t = content['mainVideoContent
']['http
://zdf
.de
/rels
/target
'] 
 116         ptmd_path = t.get('http
://zdf
.de
/rels
/streams
/ptmd
') 
 120                 'http
://zdf
.de
/rels
/streams
/ptmd
-template
'].replace( 
 121                 '{playerId}
', 'portal
') 
 123         ptmd = self._call_api( 
 124             urljoin(url, ptmd_path), player, url, video_id, 'metadata
') 
 128         for p in ptmd['priorityList
']: 
 129             formitaeten = p.get('formitaeten
') 
 130             if not isinstance(formitaeten, list): 
 132             for f in formitaeten: 
 133                 f_qualities = f.get('qualities
') 
 134                 if not isinstance(f_qualities, list): 
 136                 for quality in f_qualities: 
 137                     tracks = try_get(quality, lambda x: x['audio
']['tracks
'], list) 
 141                         self._extract_format( 
 142                             video_id, formats, track_uris, { 
 143                                 'url
': track.get('uri
'), 
 144                                 'type': f.get('type'), 
 145                                 'mimeType
': f.get('mimeType
'), 
 146                                 'quality
': quality.get('quality
'), 
 147                                 'language
': track.get('language
'), 
 149         self._sort_formats(formats) 
 153             content, lambda x: x['teaserImageRef
']['layouts
'], dict) 
 155             for layout_key, layout_url in layouts.items(): 
 156                 layout_url = url_or_none(layout_url) 
 161                     'format_id
': layout_key, 
 163                 mobj = re.search(r'(?P
<width
>\d
+)x(?P
<height
>\d
+)', layout_key) 
 166                         'width
': int(mobj.group('width
')), 
 167                         'height
': int(mobj.group('height
')), 
 169                 thumbnails.append(thumbnail) 
 174             'description
': content.get('leadParagraph
') or content.get('teasertext
'), 
 175             'duration
': int_or_none(t.get('duration
')), 
 176             'timestamp
': unified_timestamp(content.get('editorialDate
')), 
 177             'thumbnails
': thumbnails, 
 178             'subtitles
': self._extract_subtitles(ptmd), 
 182     def _extract_regular(self, url, player, video_id): 
 183         content = self._call_api( 
 184             player['content
'], player, url, video_id, 'content
') 
 185         return self._extract_entry(player['content
'], player, content, video_id) 
 187     def _extract_mobile(self, video_id): 
 188         document = self._download_json( 
 189             'https
://zdf
-cdn
.live
.cellular
.de
/mediathekV2
/document
/%s' % video_id, 
 190             video_id)['document
'] 
 192         title = document['titel
'] 
 196         for f in document['formitaeten
']: 
 197             self._extract_format(video_id, formats, format_urls, f) 
 198         self._sort_formats(formats) 
 201         teaser_bild = document.get('teaserBild
') 
 202         if isinstance(teaser_bild, dict): 
 203             for thumbnail_key, thumbnail in teaser_bild.items(): 
 204                 thumbnail_url = try_get( 
 205                     thumbnail, lambda x: x['url
'], compat_str) 
 208                         'url
': thumbnail_url, 
 210                         'width
': int_or_none(thumbnail.get('width
')), 
 211                         'height
': int_or_none(thumbnail.get('height
')), 
 217             'description
': document.get('beschreibung
'), 
 218             'duration
': int_or_none(document.get('length
')), 
 219             'timestamp
': unified_timestamp(try_get( 
 220                 document, lambda x: x['meta
']['editorialDate
'], compat_str)), 
 221             'thumbnails
': thumbnails, 
 222             'subtitles
': self._extract_subtitles(document), 
 226     def _real_extract(self, url): 
 227         video_id = self._match_id(url) 
 229         webpage = self._download_webpage(url, video_id, fatal=False) 
 231             player = self._extract_player(webpage, url, fatal=False) 
 233                 return self._extract_regular(url, player, video_id) 
 235         return self._extract_mobile(video_id) 
 238 class ZDFChannelIE(ZDFBaseIE): 
 239     _VALID_URL = r'https?
://www\
.zdf\
.de
/(?
:[^
/]+/)*(?P
<id>[^
/?
#&]+)' 
 241         'url': 'https://www.zdf.de/sport/das-aktuelle-sportstudio', 
 243             'id': 'das-aktuelle-sportstudio', 
 244             'title': 'das aktuelle sportstudio | ZDF', 
 246         'playlist_count': 21, 
 248         'url': 'https://www.zdf.de/dokumentation/planet-e', 
 251             'title': 'planet e.', 
 255         'url': 'https://www.zdf.de/filme/taunuskrimi/', 
 256         'only_matching': True, 
 260     def suitable(cls
, url
): 
 261         return False if ZDFIE
.suitable(url
) else super(ZDFChannelIE
, cls
).suitable(url
) 
 263     def _real_extract(self
, url
): 
 264         channel_id 
= self
._match
_id
(url
) 
 266         webpage 
= self
._download
_webpage
(url
, channel_id
) 
 269             self
.url_result(item_url
, ie
=ZDFIE
.ie_key()) 
 270             for item_url 
in orderedSet(re
.findall( 
 271                 r
'data-plusbar-url=["\'](http
.+?\
.html
)', webpage))] 
 273         return self.playlist_result( 
 274             entries, channel_id, self._og_search_title(webpage, fatal=False)) 
 277         player = self._extract_player(webpage, channel_id) 
 279         channel_id = self._search_regex( 
 280             r'docId\s
*:\s
*(["\'])(?P<id>(?!\1).+?)\1', webpage, 
 281             'channel id', group='id') 
 283         channel = self._call_api( 
 284             'https://api.zdf.de/content/documents/%s.json' % channel_id, 
 285             player, url, channel_id) 
 288         for module in channel['module']: 
 289             for teaser in try_get(module, lambda x: x['teaser'], list) or []: 
 291                     teaser, lambda x: x['http://zdf.de/rels/target'], dict) 
 294                 items.extend(try_get( 
 296                     lambda x: x['resultsWithVideo']['http://zdf.de/rels/search/results'], 
 298             items.extend(try_get( 
 300                 lambda x: x['filterRef']['resultsWithVideo']['http://zdf.de/rels/search/results'], 
 306             t = try_get(item, lambda x: x['http://zdf.de/rels/target'], dict) 
 309             sharing_url = t.get('http://zdf.de/rels/sharing-url') 
 310             if not sharing_url or not isinstance(sharing_url, compat_str): 
 312             if sharing_url in entry_urls: 
 314             entry_urls.add(sharing_url) 
 315             entries.append(self.url_result( 
 316                 sharing_url, ie=ZDFIE.ie_key(), video_id=t.get('id'))) 
 318         return self.playlist_result(entries, channel_id, channel.get('title'))