|
|
@ -12,11 +12,16 @@ from ..utils import ( |
|
|
|
ExtractorError, |
|
|
|
fix_xml_ampersands, |
|
|
|
int_or_none, |
|
|
|
merge_dicts, |
|
|
|
orderedSet, |
|
|
|
parse_duration, |
|
|
|
qualities, |
|
|
|
str_or_none, |
|
|
|
strip_jsonp, |
|
|
|
unified_strdate, |
|
|
|
unified_timestamp, |
|
|
|
url_or_none, |
|
|
|
urlencode_postdata, |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
@ -176,9 +181,118 @@ class NPOIE(NPOBaseIE): |
|
|
|
|
|
|
|
def _real_extract(self, url): |
|
|
|
video_id = self._match_id(url) |
|
|
|
return self._get_info(video_id) |
|
|
|
try: |
|
|
|
return self._get_info(url, video_id) |
|
|
|
except ExtractorError: |
|
|
|
return self._get_old_info(video_id) |
|
|
|
|
|
|
|
def _get_info(self, url, video_id): |
|
|
|
token = self._download_json( |
|
|
|
'https://www.npostart.nl/api/token', video_id, |
|
|
|
'Downloading token', headers={ |
|
|
|
'Referer': url, |
|
|
|
'X-Requested-With': 'XMLHttpRequest', |
|
|
|
})['token'] |
|
|
|
|
|
|
|
player = self._download_json( |
|
|
|
'https://www.npostart.nl/player/%s' % video_id, video_id, |
|
|
|
'Downloading player JSON', data=urlencode_postdata({ |
|
|
|
'autoplay': 0, |
|
|
|
'share': 1, |
|
|
|
'pageUrl': url, |
|
|
|
'hasAdConsent': 0, |
|
|
|
'_token': token, |
|
|
|
})) |
|
|
|
|
|
|
|
player_token = player['token'] |
|
|
|
|
|
|
|
format_urls = set() |
|
|
|
formats = [] |
|
|
|
for profile in ('hls', 'dash-widevine', 'dash-playready', 'smooth'): |
|
|
|
streams = self._download_json( |
|
|
|
'https://start-player.npo.nl/video/%s/streams' % video_id, |
|
|
|
video_id, 'Downloading %s profile JSON' % profile, fatal=False, |
|
|
|
query={ |
|
|
|
'profile': profile, |
|
|
|
'quality': 'npo', |
|
|
|
'tokenId': player_token, |
|
|
|
'streamType': 'broadcast', |
|
|
|
}) |
|
|
|
if not streams: |
|
|
|
continue |
|
|
|
stream = streams.get('stream') |
|
|
|
if not isinstance(stream, dict): |
|
|
|
continue |
|
|
|
stream_url = url_or_none(stream.get('src')) |
|
|
|
if not stream_url or stream_url in format_urls: |
|
|
|
continue |
|
|
|
format_urls.add(stream_url) |
|
|
|
if stream.get('protection') is not None: |
|
|
|
continue |
|
|
|
stream_type = stream.get('type') |
|
|
|
stream_ext = determine_ext(stream_url) |
|
|
|
if stream_type == 'application/dash+xml' or stream_ext == 'mpd': |
|
|
|
formats.extend(self._extract_mpd_formats( |
|
|
|
stream_url, video_id, mpd_id='dash', fatal=False)) |
|
|
|
elif stream_type == 'application/vnd.apple.mpegurl' or stream_ext == 'm3u8': |
|
|
|
formats.extend(self._extract_m3u8_formats( |
|
|
|
stream_url, video_id, ext='mp4', |
|
|
|
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)) |
|
|
|
elif '.ism/Manifest' in stream_url: |
|
|
|
formats.extend(self._extract_ism_formats( |
|
|
|
stream_url, video_id, ism_id='mss', fatal=False)) |
|
|
|
else: |
|
|
|
formats.append({ |
|
|
|
'url': stream_url, |
|
|
|
}) |
|
|
|
|
|
|
|
self._sort_formats(formats) |
|
|
|
|
|
|
|
info = { |
|
|
|
'id': video_id, |
|
|
|
'title': video_id, |
|
|
|
'formats': formats, |
|
|
|
} |
|
|
|
|
|
|
|
def _get_info(self, video_id): |
|
|
|
embed_url = url_or_none(player.get('embedUrl')) |
|
|
|
if embed_url: |
|
|
|
webpage = self._download_webpage( |
|
|
|
embed_url, video_id, 'Downloading embed page', fatal=False) |
|
|
|
if webpage: |
|
|
|
video = self._parse_json( |
|
|
|
self._search_regex( |
|
|
|
r'\bvideo\s*=\s*({.+?})\s*;', webpage, 'video', |
|
|
|
default='{}'), video_id) |
|
|
|
if video: |
|
|
|
title = video.get('episodeTitle') |
|
|
|
subtitles = {} |
|
|
|
subtitles_list = video.get('subtitles') |
|
|
|
if isinstance(subtitles_list, list): |
|
|
|
for cc in subtitles_list: |
|
|
|
cc_url = url_or_none(cc.get('src')) |
|
|
|
if not cc_url: |
|
|
|
continue |
|
|
|
lang = str_or_none(cc.get('language')) or 'nl' |
|
|
|
subtitles.setdefault(lang, []).append({ |
|
|
|
'url': cc_url, |
|
|
|
}) |
|
|
|
return merge_dicts({ |
|
|
|
'title': title, |
|
|
|
'description': video.get('description'), |
|
|
|
'thumbnail': url_or_none( |
|
|
|
video.get('still_image_url') or video.get('orig_image_url')), |
|
|
|
'duration': int_or_none(video.get('duration')), |
|
|
|
'timestamp': unified_timestamp(video.get('broadcastDate')), |
|
|
|
'creator': video.get('channel'), |
|
|
|
'series': video.get('title'), |
|
|
|
'episode': title, |
|
|
|
'episode_number': int_or_none(video.get('episodeNumber')), |
|
|
|
'subtitles': subtitles, |
|
|
|
}, info) |
|
|
|
|
|
|
|
return info |
|
|
|
|
|
|
|
def _get_old_info(self, video_id): |
|
|
|
metadata = self._download_json( |
|
|
|
'http://e.omroep.nl/metadata/%s' % video_id, |
|
|
|
video_id, |
|
|
@ -280,7 +394,7 @@ class NPOIE(NPOBaseIE): |
|
|
|
# JSON |
|
|
|
else: |
|
|
|
video_url = stream_info.get('url') |
|
|
|
if not video_url or video_url in urls: |
|
|
|
if not video_url or 'vodnotavailable.' in video_url or video_url in urls: |
|
|
|
continue |
|
|
|
urls.add(video_url) |
|
|
|
if determine_ext(video_url) == 'm3u8': |
|
|
|