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
') 
  44     _GEO_COUNTRIES = ['DE
'] 
  47         'url
': 'https
://www
.zdf
.de
/dokumentation
/terra
-x
/die
-magie
-der
-farben
-von
-koenigspurpur
-und
-jeansblau
-100.html
', 
  49             'id': 'die
-magie
-der
-farben
-von
-koenigspurpur
-und
-jeansblau
-100', 
  51             'title
': 'Die Magie der 
Farben (2/2)', 
  52             'description
': 'md5
:a89da10c928c6235401066b60a6d5c1a
', 
  54             'timestamp
': 1465021200, 
  55             'upload_date
': '20160604', 
  58         'url
': 'https
://www
.zdf
.de
/service
-und
-hilfe
/die
-neue
-zdf
-mediathek
/zdfmediathek
-trailer
-100.html
', 
  59         'only_matching
': True, 
  61         'url
': 'https
://www
.zdf
.de
/filme
/taunuskrimi
/die
-lebenden
-und
-die
-toten
-1---ein
-taunuskrimi
-100.html
', 
  62         'only_matching
': True, 
  64         'url
': 'https
://www
.zdf
.de
/dokumentation
/planet
-e
/planet
-e
-uebersichtsseite
-weitere
-dokumentationen
-von
-planet
-e
-100.html
', 
  65         'only_matching
': True, 
  69     def _extract_subtitles(src): 
  71         for caption in try_get(src, lambda x: x['captions
'], list) or []: 
  72             subtitle_url = url_or_none(caption.get('uri
')) 
  74                 lang = caption.get('language
', 'deu
') 
  75                 subtitles.setdefault(lang, []).append({ 
  80     def _extract_format(self, video_id, formats, format_urls, meta): 
  81         format_url = url_or_none(meta.get('url
')) 
  84         if format_url in format_urls: 
  86         format_urls.add(format_url) 
  87         mime_type = meta.get('mimeType
') 
  88         ext = determine_ext(format_url) 
  89         if mime_type == 'application
/x
-mpegURL
' or ext == 'm3u8
': 
  90             formats.extend(self._extract_m3u8_formats( 
  91                 format_url, video_id, 'mp4
', m3u8_id='hls
', 
  92                 entry_protocol='m3u8_native
', fatal=False)) 
  93         elif mime_type == 'application
/f4m
+xml
' or ext == 'f4m
': 
  94             formats.extend(self._extract_f4m_formats( 
  95                 update_url_query(format_url, {'hdcore
': '3.7.0'}), video_id, f4m_id='hds
', fatal=False)) 
  97             f = parse_codecs(meta.get('mimeCodec
')) 
  99             for p in (meta.get('type'), meta.get('quality
')): 
 100                 if p and isinstance(p, compat_str): 
 104                 'format_id
': '-'.join(format_id), 
 105                 'format_note
': meta.get('quality
'), 
 106                 'language
': meta.get('language
'), 
 107                 'quality
': qualities(self._QUALITIES)(meta.get('quality
')), 
 112     def _extract_entry(self, url, player, content, video_id): 
 113         title = content.get('title
') or content['teaserHeadline
'] 
 115         t = content['mainVideoContent
']['http
://zdf
.de
/rels
/target
'] 
 117         ptmd_path = t.get('http
://zdf
.de
/rels
/streams
/ptmd
') 
 121                 'http
://zdf
.de
/rels
/streams
/ptmd
-template
'].replace( 
 122                 '{playerId}
', 'portal
') 
 124         ptmd = self._call_api( 
 125             urljoin(url, ptmd_path), player, url, video_id, 'metadata
') 
 129         for p in ptmd['priorityList
']: 
 130             formitaeten = p.get('formitaeten
') 
 131             if not isinstance(formitaeten, list): 
 133             for f in formitaeten: 
 134                 f_qualities = f.get('qualities
') 
 135                 if not isinstance(f_qualities, list): 
 137                 for quality in f_qualities: 
 138                     tracks = try_get(quality, lambda x: x['audio
']['tracks
'], list) 
 142                         self._extract_format( 
 143                             video_id, formats, track_uris, { 
 144                                 'url
': track.get('uri
'), 
 145                                 'type': f.get('type'), 
 146                                 'mimeType
': f.get('mimeType
'), 
 147                                 'quality
': quality.get('quality
'), 
 148                                 'language
': track.get('language
'), 
 150         self._sort_formats(formats) 
 154             content, lambda x: x['teaserImageRef
']['layouts
'], dict) 
 156             for layout_key, layout_url in layouts.items(): 
 157                 layout_url = url_or_none(layout_url) 
 162                     'format_id
': layout_key, 
 164                 mobj = re.search(r'(?P
<width
>\d
+)x(?P
<height
>\d
+)', layout_key) 
 167                         'width
': int(mobj.group('width
')), 
 168                         'height
': int(mobj.group('height
')), 
 170                 thumbnails.append(thumbnail) 
 175             'description
': content.get('leadParagraph
') or content.get('teasertext
'), 
 176             'duration
': int_or_none(t.get('duration
')), 
 177             'timestamp
': unified_timestamp(content.get('editorialDate
')), 
 178             'thumbnails
': thumbnails, 
 179             'subtitles
': self._extract_subtitles(ptmd), 
 183     def _extract_regular(self, url, player, video_id): 
 184         content = self._call_api( 
 185             player['content
'], player, url, video_id, 'content
') 
 186         return self._extract_entry(player['content
'], player, content, video_id) 
 188     def _extract_mobile(self, video_id): 
 189         document = self._download_json( 
 190             'https
://zdf
-cdn
.live
.cellular
.de
/mediathekV2
/document
/%s' % video_id, 
 191             video_id)['document
'] 
 193         title = document['titel
'] 
 197         for f in document['formitaeten
']: 
 198             self._extract_format(video_id, formats, format_urls, f) 
 199         self._sort_formats(formats) 
 202         teaser_bild = document.get('teaserBild
') 
 203         if isinstance(teaser_bild, dict): 
 204             for thumbnail_key, thumbnail in teaser_bild.items(): 
 205                 thumbnail_url = try_get( 
 206                     thumbnail, lambda x: x['url
'], compat_str) 
 209                         'url
': thumbnail_url, 
 211                         'width
': int_or_none(thumbnail.get('width
')), 
 212                         'height
': int_or_none(thumbnail.get('height
')), 
 218             'description
': document.get('beschreibung
'), 
 219             'duration
': int_or_none(document.get('length
')), 
 220             'timestamp
': unified_timestamp(try_get( 
 221                 document, lambda x: x['meta
']['editorialDate
'], compat_str)), 
 222             'thumbnails
': thumbnails, 
 223             'subtitles
': self._extract_subtitles(document), 
 227     def _real_extract(self, url): 
 228         video_id = self._match_id(url) 
 230         webpage = self._download_webpage(url, video_id, fatal=False) 
 232             player = self._extract_player(webpage, url, fatal=False) 
 234                 return self._extract_regular(url, player, video_id) 
 236         return self._extract_mobile(video_id) 
 239 class ZDFChannelIE(ZDFBaseIE): 
 240     _VALID_URL = r'https?
://www\
.zdf\
.de
/(?
:[^
/]+/)*(?P
<id>[^
/?
#&]+)' 
 242         'url': 'https://www.zdf.de/sport/das-aktuelle-sportstudio', 
 244             'id': 'das-aktuelle-sportstudio', 
 245             'title': 'das aktuelle sportstudio | ZDF', 
 247         'playlist_mincount': 23, 
 249         'url': 'https://www.zdf.de/dokumentation/planet-e', 
 252             'title': 'planet e.', 
 254         'playlist_mincount': 50, 
 256         'url': 'https://www.zdf.de/filme/taunuskrimi/', 
 257         'only_matching': True, 
 261     def suitable(cls
, url
): 
 262         return False if ZDFIE
.suitable(url
) else super(ZDFChannelIE
, cls
).suitable(url
) 
 264     def _real_extract(self
, url
): 
 265         channel_id 
= self
._match
_id
(url
) 
 267         webpage 
= self
._download
_webpage
(url
, channel_id
) 
 270             self
.url_result(item_url
, ie
=ZDFIE
.ie_key()) 
 271             for item_url 
in orderedSet(re
.findall( 
 272                 r
'data-plusbar-url=["\'](http
.+?\
.html
)', webpage))] 
 274         return self.playlist_result( 
 275             entries, channel_id, self._og_search_title(webpage, fatal=False)) 
 278         player = self._extract_player(webpage, channel_id) 
 280         channel_id = self._search_regex( 
 281             r'docId\s
*:\s
*(["\'])(?P<id>(?!\1).+?)\1', webpage, 
 282             'channel id', group='id') 
 284         channel = self._call_api( 
 285             'https://api.zdf.de/content/documents/%s.json' % channel_id, 
 286             player, url, channel_id) 
 289         for module in channel['module']: 
 290             for teaser in try_get(module, lambda x: x['teaser'], list) or []: 
 292                     teaser, lambda x: x['http://zdf.de/rels/target'], dict) 
 295                 items.extend(try_get( 
 297                     lambda x: x['resultsWithVideo']['http://zdf.de/rels/search/results'], 
 299             items.extend(try_get( 
 301                 lambda x: x['filterRef']['resultsWithVideo']['http://zdf.de/rels/search/results'], 
 307             t = try_get(item, lambda x: x['http://zdf.de/rels/target'], dict) 
 310             sharing_url = t.get('http://zdf.de/rels/sharing-url') 
 311             if not sharing_url or not isinstance(sharing_url, compat_str): 
 313             if sharing_url in entry_urls: 
 315             entry_urls.add(sharing_url) 
 316             entries.append(self.url_result( 
 317                 sharing_url, ie=ZDFIE.ie_key(), video_id=t.get('id'))) 
 319         return self.playlist_result(entries, channel_id, channel.get('title'))