2024-03-05 13:11:49 +00:00
|
|
|
# coding: utf-8
|
2014-06-26 18:30:44 +00:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2024-03-01 13:12:51 +00:00
|
|
|
import json
|
2024-03-05 12:43:56 +00:00
|
|
|
import re
|
2015-07-16 17:54:43 +00:00
|
|
|
|
2015-02-12 19:36:54 +00:00
|
|
|
from .common import InfoExtractor
|
2024-03-05 12:55:59 +00:00
|
|
|
from ..utils import ExtractorError
|
2014-06-26 18:30:44 +00:00
|
|
|
|
|
|
|
|
2024-03-01 12:24:48 +00:00
|
|
|
class NPOIE(InfoExtractor):
|
2015-07-16 17:21:04 +00:00
|
|
|
IE_NAME = 'npo'
|
2024-03-01 12:24:48 +00:00
|
|
|
IE_DESC = 'npo.nl'
|
2024-03-06 11:53:37 +00:00
|
|
|
_VALID_URL = r'https?://(?:www\.)?npo\.nl/.*'
|
2014-06-26 18:30:44 +00:00
|
|
|
|
2017-03-01 15:14:46 +00:00
|
|
|
_TESTS = [{
|
2024-03-01 09:36:03 +00:00
|
|
|
'url': 'https://npo.nl/start/serie/zembla/seizoen-2015/wie-is-de-mol-2/',
|
2024-03-01 13:12:51 +00:00
|
|
|
# TODO fill in other test attributes
|
2017-03-01 15:14:46 +00:00
|
|
|
}, {
|
2024-03-01 09:36:03 +00:00
|
|
|
'url': 'https://npo.nl/start/serie/vpro-tegenlicht/seizoen-11/zwart-geld-de-toekomst-komt-uit-afrika',
|
2017-03-01 15:14:46 +00:00
|
|
|
'md5': 'f8065e4e5a7824068ed3c7e783178f2c',
|
|
|
|
'info_dict': {
|
|
|
|
'id': 'VPWON_1169289',
|
|
|
|
'ext': 'm4v',
|
|
|
|
'title': 'Tegenlicht: Zwart geld. De toekomst komt uit Afrika',
|
|
|
|
'description': 'md5:52cf4eefbc96fffcbdc06d024147abea',
|
|
|
|
'upload_date': '20130225',
|
|
|
|
'duration': 3000,
|
2014-12-20 12:30:56 +00:00
|
|
|
},
|
2017-03-01 15:14:46 +00:00
|
|
|
}]
|
2014-06-26 18:30:44 +00:00
|
|
|
|
2024-03-01 12:24:48 +00:00
|
|
|
def _get_token(self, video_id):
|
|
|
|
return self._download_json(
|
|
|
|
'https://npo.nl/start/api/domain/player-token?productId=%s' % video_id,
|
|
|
|
video_id,
|
|
|
|
note='Downloading token')['token']
|
2018-06-09 17:26:16 +00:00
|
|
|
|
2014-06-26 18:30:44 +00:00
|
|
|
def _real_extract(self, url):
|
2024-03-01 13:12:51 +00:00
|
|
|
# You might want to use removesuffix here,
|
|
|
|
# but removesuffix is introduced in Python 3.9
|
|
|
|
# and youtube-dl supports Python 3.2+
|
|
|
|
if url.endswith('/afspelen'):
|
|
|
|
url = url[:-9]
|
|
|
|
elif url.endswith('/afspelen/'):
|
|
|
|
url = url[:-10]
|
2024-03-01 14:05:30 +00:00
|
|
|
url = url.rstrip('/')
|
2024-03-01 13:12:51 +00:00
|
|
|
slug = url.split('/')[-1]
|
2024-03-06 10:52:08 +00:00
|
|
|
|
|
|
|
program_metadata = self._download_json('https://npo.nl/start/api/domain/program-detail',
|
|
|
|
slug,
|
|
|
|
query={'slug': slug})
|
|
|
|
product_id = program_metadata.get('productId')
|
|
|
|
images = program_metadata.get('images')
|
|
|
|
thumbnail = None
|
|
|
|
for image in images:
|
|
|
|
thumbnail = image.get('url')
|
|
|
|
break
|
|
|
|
title = program_metadata.get('title')
|
|
|
|
descriptions = program_metadata.get('description', {})
|
|
|
|
description = descriptions.get('long') or descriptions.get('short') or descriptions.get('brief')
|
|
|
|
duration = program_metadata.get('durationInSeconds')
|
|
|
|
|
2024-03-01 13:12:51 +00:00
|
|
|
if not product_id:
|
|
|
|
raise ExtractorError('No productId found for slug: %s' % slug)
|
|
|
|
|
2024-03-03 16:47:15 +00:00
|
|
|
formats = self._download_by_product_id(product_id, slug, url)
|
|
|
|
|
|
|
|
return {
|
|
|
|
'id': slug,
|
|
|
|
'formats': formats,
|
|
|
|
'title': title or slug,
|
2024-03-06 10:52:08 +00:00
|
|
|
'description': description or title or slug,
|
2024-03-03 16:47:15 +00:00
|
|
|
'thumbnail': thumbnail,
|
2024-03-06 10:52:08 +00:00
|
|
|
'duration': duration,
|
2024-03-03 16:47:15 +00:00
|
|
|
}
|
2024-03-01 13:12:51 +00:00
|
|
|
|
2024-03-03 16:47:15 +00:00
|
|
|
def _download_by_product_id(self, product_id, slug, url=None):
|
|
|
|
token = self._get_token(product_id)
|
2024-03-01 14:28:14 +00:00
|
|
|
formats = []
|
|
|
|
for profile in (
|
2024-03-06 11:22:27 +00:00
|
|
|
'dash',
|
|
|
|
# 'hls' is available too, but implementing it doesn't add much
|
|
|
|
# As far as I know 'dash' is always available
|
2024-03-01 14:28:14 +00:00
|
|
|
):
|
|
|
|
stream_link = self._download_json(
|
|
|
|
'https://prod.npoplayer.nl/stream-link', video_id=slug,
|
|
|
|
data=json.dumps({
|
|
|
|
'profileName': profile,
|
|
|
|
'drmType': 'widevine',
|
2024-03-03 16:47:15 +00:00
|
|
|
'referrerUrl': url or '',
|
2024-03-01 14:28:14 +00:00
|
|
|
}).encode('utf8'),
|
|
|
|
headers={
|
|
|
|
'Authorization': token,
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
}
|
|
|
|
)
|
|
|
|
stream_url = stream_link.get('stream', {}).get('streamURL')
|
|
|
|
formats.extend(self._extract_mpd_formats(stream_url, slug, mpd_id='dash', fatal=False))
|
2024-03-03 16:47:15 +00:00
|
|
|
return formats
|
|
|
|
|
|
|
|
|
|
|
|
class BNNVaraIE(NPOIE):
|
|
|
|
IE_NAME = 'bnnvara'
|
|
|
|
IE_DESC = 'bnnvara.nl'
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?bnnvara\.nl/videos/[0-9]*'
|
2024-03-05 12:43:56 +00:00
|
|
|
_TESTS = [{
|
|
|
|
'url': 'https://www.bnnvara.nl/videos/27455',
|
2024-03-06 10:52:08 +00:00
|
|
|
# TODO fill in other test attributes
|
2024-03-05 12:43:56 +00:00
|
|
|
}]
|
2024-03-03 16:47:15 +00:00
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
url = url.rstrip('/')
|
|
|
|
video_id = url.split('/')[-1]
|
|
|
|
|
|
|
|
media = self._download_json('https://api.bnnvara.nl/bff/graphql',
|
|
|
|
video_id,
|
|
|
|
data=json.dumps(
|
|
|
|
{
|
|
|
|
'operationName': 'getMedia',
|
|
|
|
'variables': {
|
|
|
|
'id': video_id,
|
|
|
|
'hasAdConsent': False,
|
|
|
|
'atInternetId': 70
|
|
|
|
},
|
|
|
|
'query': 'query getMedia($id: ID!, $mediaUrl: String, $hasAdConsent: Boolean!, $atInternetId: Int) {\n player(\n id: $id\n mediaUrl: $mediaUrl\n hasAdConsent: $hasAdConsent\n atInternetId: $atInternetId\n ) {\n ... on PlayerSucces {\n brand {\n name\n slug\n broadcastsEnabled\n __typename\n }\n title\n programTitle\n pomsProductId\n broadcasters {\n name\n __typename\n }\n duration\n classifications {\n title\n imageUrl\n type\n __typename\n }\n image {\n title\n url\n __typename\n }\n cta {\n title\n url\n __typename\n }\n genres {\n name\n __typename\n }\n subtitles {\n url\n language\n __typename\n }\n sources {\n name\n url\n ratio\n __typename\n }\n type\n token\n __typename\n }\n ... on PlayerError {\n error\n __typename\n }\n __typename\n }\n}'
|
|
|
|
}).encode('utf8'),
|
|
|
|
headers={
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
})
|
|
|
|
product_id = media.get('data', {}).get('player', {}).get('pomsProductId')
|
|
|
|
|
|
|
|
formats = self._download_by_product_id(product_id, video_id)
|
2014-06-26 18:30:44 +00:00
|
|
|
|
|
|
|
return {
|
2024-03-03 16:47:15 +00:00
|
|
|
'id': product_id,
|
|
|
|
'title': media.get('data', {}).get('player', {}).get('title'),
|
2024-03-01 14:28:14 +00:00
|
|
|
'formats': formats,
|
2024-03-03 16:47:15 +00:00
|
|
|
'thumbnail': media.get('data', {}).get('player', {}).get('image').get('url'),
|
2014-06-26 18:30:44 +00:00
|
|
|
}
|
2024-03-05 12:43:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ONIE(NPOIE):
|
|
|
|
IE_NAME = 'on'
|
|
|
|
IE_DESC = 'ongehoordnederland.tv'
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?ongehoordnederland.tv/.*'
|
|
|
|
_TESTS = [{
|
|
|
|
'url': 'https://ongehoordnederland.tv/2024/03/01/korte-clips/heeft-preppen-zin-betwijfel-dat-je-daar-echt-iets-aan-zult-hebben-bij-oorlog-lydia-daniel/',
|
2024-03-06 10:52:08 +00:00
|
|
|
# TODO fill in other test attributes
|
2024-03-05 12:43:56 +00:00
|
|
|
}]
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
video_id = url.rstrip('/').split('/')[-1]
|
|
|
|
page, _ = self._download_webpage_handle(url, video_id)
|
|
|
|
results = re.findall("page: '(.+)'", page)
|
|
|
|
formats = []
|
|
|
|
for result in results:
|
|
|
|
formats.extend(self._download_by_product_id(result, video_id))
|
|
|
|
|
|
|
|
if not formats:
|
2024-03-06 11:32:34 +00:00
|
|
|
raise ExtractorError('Could not find a POMS product id in the provided URL, '
|
|
|
|
'perhaps because all stream URLs are DRM protected.')
|
2024-03-05 12:43:56 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
'id': video_id,
|
|
|
|
'title': video_id,
|
|
|
|
'formats': formats,
|
|
|
|
}
|
2024-03-05 12:55:59 +00:00
|
|
|
|
|
|
|
|
2024-03-05 13:04:03 +00:00
|
|
|
class ZAPPIE(NPOIE):
|
|
|
|
IE_NAME = 'zapp'
|
|
|
|
IE_DESC = 'zapp.nl'
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?zapp.nl/.*'
|
|
|
|
|
|
|
|
_TESTS = [{
|
|
|
|
'url': 'https://www.zapp.nl/programmas/zappsport/gemist/AT_300003973',
|
2024-03-06 10:52:08 +00:00
|
|
|
# TODO fill in other test attributes
|
2024-03-05 13:04:03 +00:00
|
|
|
}]
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
video_id = url.rstrip('/').split('/')[-1]
|
|
|
|
|
|
|
|
formats = self._download_by_product_id(url, video_id)
|
|
|
|
|
|
|
|
return {
|
|
|
|
'id': video_id,
|
|
|
|
'title': video_id,
|
|
|
|
'formats': formats,
|
|
|
|
}
|
2024-03-06 11:22:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SchoolTVIE(NPOIE):
|
|
|
|
IE_NAME = 'schooltv'
|
|
|
|
IE_DESC = 'schooltv.nl'
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?schooltv.nl/item/.*'
|
|
|
|
|
|
|
|
_TESTS = [{
|
|
|
|
'url': 'https://schooltv.nl/item/zapp-music-challenge-2015-zapp-music-challenge-2015',
|
|
|
|
# TODO fill in other test attributes
|
|
|
|
}]
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
video_id = url.rstrip('/').split('/')[-1]
|
|
|
|
|
2024-03-06 11:32:34 +00:00
|
|
|
# TODO Find out how we could obtain this automatically
|
|
|
|
# Otherwise this extractor might break each time SchoolTV deploys a new release
|
2024-03-06 11:22:27 +00:00
|
|
|
build_id = 'b7eHUzAVO7wHXCopYxQhV'
|
|
|
|
|
|
|
|
metadata_url = 'https://schooltv.nl/_next/data/' \
|
|
|
|
+ build_id \
|
|
|
|
+ '/item/' \
|
|
|
|
+ video_id + '.json'
|
|
|
|
|
|
|
|
metadata = self._download_json(metadata_url,
|
|
|
|
video_id).get('pageProps', {}).get('data', {})
|
|
|
|
|
|
|
|
formats = self._download_by_product_id(metadata.get('poms_mid'), video_id)
|
|
|
|
|
|
|
|
if not formats:
|
2024-03-06 11:32:34 +00:00
|
|
|
raise ExtractorError('Could not find a POMS product id in the provided URL, '
|
|
|
|
'perhaps because all stream URLs are DRM protected.')
|
2024-03-06 11:22:27 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
'id': video_id,
|
|
|
|
'title': metadata.get('title', '') + ' - ' + metadata.get('subtitle', ''),
|
|
|
|
'description': metadata.get('description') or metadata.get('short_description'),
|
|
|
|
'formats': formats,
|
|
|
|
}
|
2024-03-06 11:53:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class HetKlokhuisIE(NPOIE):
|
|
|
|
...
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
class VPROIE(NPOIE):
|
|
|
|
IE_NAME = 'vpro'
|
|
|
|
IE_DESC = 'vpro.nl'
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?vpro.nl/.*'
|
|
|
|
_TESTS = [{
|
|
|
|
'url': 'https://www.vpro.nl/programmas/tegenlicht/kijk/afleveringen/2015-2016/offline-als-luxe.html',
|
|
|
|
# TODO fill in other test attributes
|
|
|
|
}]
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
video_id = url.rstrip('/').split('/')[-1]
|
|
|
|
page, _ = self._download_webpage_handle(url, video_id)
|
|
|
|
results = re.findall(r'data-media-id="(.+_.+)"\s', page)
|
|
|
|
formats = []
|
|
|
|
for result in results:
|
|
|
|
formats.extend(self._download_by_product_id(result, video_id))
|
|
|
|
break # TODO find a better solution, VPRO pages can have multiple videos embedded
|
|
|
|
|
|
|
|
if not formats:
|
|
|
|
raise ExtractorError('Could not find a POMS product id in the provided URL, '
|
|
|
|
'perhaps because all stream URLs are DRM protected.')
|
|
|
|
|
|
|
|
return {
|
|
|
|
'id': video_id,
|
|
|
|
'title': video_id,
|
|
|
|
'formats': formats,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class WNLIE(NPOIE):
|
|
|
|
...
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
class AndereTijdenIE(NPOIE):
|
|
|
|
...
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
...
|
|
|
|
|