|
|
@ -12,6 +12,7 @@ from ..utils import (
|
|
|
|
float_or_none,
|
|
|
|
float_or_none,
|
|
|
|
mimetype2ext,
|
|
|
|
mimetype2ext,
|
|
|
|
str_or_none,
|
|
|
|
str_or_none,
|
|
|
|
|
|
|
|
traverse_obj,
|
|
|
|
try_get,
|
|
|
|
try_get,
|
|
|
|
unified_timestamp,
|
|
|
|
unified_timestamp,
|
|
|
|
update_url_query,
|
|
|
|
update_url_query,
|
|
|
@ -19,6 +20,9 @@ from ..utils import (
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SERIES_API = 'https://production-cdn.dr-massive.com/api/page?device=web_browser&item_detail_expand=all&lang=da&max_list_prefetch=3&path=%s'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DRTVIE(InfoExtractor):
|
|
|
|
class DRTVIE(InfoExtractor):
|
|
|
|
_VALID_URL = r'''(?x)
|
|
|
|
_VALID_URL = r'''(?x)
|
|
|
|
https?://
|
|
|
|
https?://
|
|
|
@ -141,13 +145,13 @@ class DRTVIE(InfoExtractor):
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
def _real_extract(self, url):
|
|
|
|
video_id = self._match_id(url)
|
|
|
|
raw_video_id = self._match_id(url)
|
|
|
|
|
|
|
|
|
|
|
|
webpage = self._download_webpage(url, video_id)
|
|
|
|
webpage = self._download_webpage(url, raw_video_id)
|
|
|
|
|
|
|
|
|
|
|
|
if '>Programmet er ikke længere tilgængeligt' in webpage:
|
|
|
|
if '>Programmet er ikke længere tilgængeligt' in webpage:
|
|
|
|
raise ExtractorError(
|
|
|
|
raise ExtractorError(
|
|
|
|
'Video %s is not available' % video_id, expected=True)
|
|
|
|
'Video %s is not available' % raw_video_id, expected=True)
|
|
|
|
|
|
|
|
|
|
|
|
video_id = self._search_regex(
|
|
|
|
video_id = self._search_regex(
|
|
|
|
(r'data-(?:material-identifier|episode-slug)="([^"]+)"',
|
|
|
|
(r'data-(?:material-identifier|episode-slug)="([^"]+)"',
|
|
|
@ -182,6 +186,10 @@ class DRTVIE(InfoExtractor):
|
|
|
|
data = self._download_json(
|
|
|
|
data = self._download_json(
|
|
|
|
programcard_url, video_id, 'Downloading video JSON', query=query)
|
|
|
|
programcard_url, video_id, 'Downloading video JSON', query=query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
supplementary_data = self._download_json(
|
|
|
|
|
|
|
|
SERIES_API % f'/episode/{raw_video_id}', raw_video_id,
|
|
|
|
|
|
|
|
default={}) if re.search(r'_\d+$', raw_video_id) else {}
|
|
|
|
|
|
|
|
|
|
|
|
title = str_or_none(data.get('Title')) or re.sub(
|
|
|
|
title = str_or_none(data.get('Title')) or re.sub(
|
|
|
|
r'\s*\|\s*(?:TV\s*\|\s*DR|DRTV)$', '',
|
|
|
|
r'\s*\|\s*(?:TV\s*\|\s*DR|DRTV)$', '',
|
|
|
|
self._og_search_title(webpage))
|
|
|
|
self._og_search_title(webpage))
|
|
|
@ -313,8 +321,8 @@ class DRTVIE(InfoExtractor):
|
|
|
|
'season': str_or_none(data.get('SeasonTitle')),
|
|
|
|
'season': str_or_none(data.get('SeasonTitle')),
|
|
|
|
'season_number': int_or_none(data.get('SeasonNumber')),
|
|
|
|
'season_number': int_or_none(data.get('SeasonNumber')),
|
|
|
|
'season_id': str_or_none(data.get('SeasonUrn')),
|
|
|
|
'season_id': str_or_none(data.get('SeasonUrn')),
|
|
|
|
'episode': str_or_none(data.get('EpisodeTitle')),
|
|
|
|
'episode': traverse_obj(supplementary_data, ('entries', 0, 'item', 'contextualTitle')) or str_or_none(data.get('EpisodeTitle')),
|
|
|
|
'episode_number': int_or_none(data.get('EpisodeNumber')),
|
|
|
|
'episode_number': traverse_obj(supplementary_data, ('entries', 0, 'item', 'episodeNumber')) or int_or_none(data.get('EpisodeNumber')),
|
|
|
|
'release_year': int_or_none(data.get('ProductionYear')),
|
|
|
|
'release_year': int_or_none(data.get('ProductionYear')),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -372,3 +380,92 @@ class DRTVLiveIE(InfoExtractor):
|
|
|
|
'formats': formats,
|
|
|
|
'formats': formats,
|
|
|
|
'is_live': True,
|
|
|
|
'is_live': True,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DRTVSeasonIE(InfoExtractor):
|
|
|
|
|
|
|
|
IE_NAME = 'drtv:season'
|
|
|
|
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?(?:dr\.dk|dr-massive\.com)/drtv/saeson/(?P<display_id>[\w-]+)_(?P<id>\d+)'
|
|
|
|
|
|
|
|
_GEO_COUNTRIES = ['DK']
|
|
|
|
|
|
|
|
_TESTS = [{
|
|
|
|
|
|
|
|
'url': 'https://www.dr.dk/drtv/saeson/frank-and-kastaniegaarden_9008',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '9008',
|
|
|
|
|
|
|
|
'display_id': 'frank-and-kastaniegaarden',
|
|
|
|
|
|
|
|
'title': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
'series': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'playlist_mincount': 8
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
'url': 'https://www.dr.dk/drtv/saeson/frank-and-kastaniegaarden_8761',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '8761',
|
|
|
|
|
|
|
|
'display_id': 'frank-and-kastaniegaarden',
|
|
|
|
|
|
|
|
'title': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
'series': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'playlist_mincount': 19
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
|
|
|
|
display_id, season_id = self._match_valid_url(url).group('display_id', 'id')
|
|
|
|
|
|
|
|
data = self._download_json(SERIES_API % f'/saeson/{display_id}_{season_id}', display_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entries = [{
|
|
|
|
|
|
|
|
'_type': 'url',
|
|
|
|
|
|
|
|
'url': f'https://www.dr.dk/drtv{episode["path"]}',
|
|
|
|
|
|
|
|
'ie_key': DRTVIE.ie_key(),
|
|
|
|
|
|
|
|
'title': episode.get('title'),
|
|
|
|
|
|
|
|
'episode': episode.get('episodeName'),
|
|
|
|
|
|
|
|
'description': episode.get('shortDescription'),
|
|
|
|
|
|
|
|
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber')),
|
|
|
|
|
|
|
|
'episode_number': episode.get('episodeNumber'),
|
|
|
|
|
|
|
|
} for episode in traverse_obj(data, ('entries', 0, 'item', 'episodes', 'items'))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
'_type': 'playlist',
|
|
|
|
|
|
|
|
'id': season_id,
|
|
|
|
|
|
|
|
'display_id': display_id,
|
|
|
|
|
|
|
|
'title': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'entries': entries,
|
|
|
|
|
|
|
|
'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber'))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DRTVSeriesIE(InfoExtractor):
|
|
|
|
|
|
|
|
IE_NAME = 'drtv:series'
|
|
|
|
|
|
|
|
_VALID_URL = r'https?://(?:www\.)?(?:dr\.dk|dr-massive\.com)/drtv/serie/(?P<display_id>[\w-]+)_(?P<id>\d+)'
|
|
|
|
|
|
|
|
_GEO_COUNTRIES = ['DK']
|
|
|
|
|
|
|
|
_TESTS = [{
|
|
|
|
|
|
|
|
'url': 'https://www.dr.dk/drtv/serie/frank-and-kastaniegaarden_6954',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '6954',
|
|
|
|
|
|
|
|
'display_id': 'frank-and-kastaniegaarden',
|
|
|
|
|
|
|
|
'title': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
'series': 'Frank & Kastaniegaarden',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'playlist_mincount': 15
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
|
|
|
|
display_id, series_id = self._match_valid_url(url).group('display_id', 'id')
|
|
|
|
|
|
|
|
data = self._download_json(SERIES_API % f'/serie/{display_id}_{series_id}', display_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entries = [{
|
|
|
|
|
|
|
|
'_type': 'url',
|
|
|
|
|
|
|
|
'url': f'https://www.dr.dk/drtv{season.get("path")}',
|
|
|
|
|
|
|
|
'ie_key': DRTVSeasonIE.ie_key(),
|
|
|
|
|
|
|
|
'title': season.get('title'),
|
|
|
|
|
|
|
|
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber'))
|
|
|
|
|
|
|
|
} for season in traverse_obj(data, ('entries', 0, 'item', 'show', 'seasons', 'items'))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
'_type': 'playlist',
|
|
|
|
|
|
|
|
'id': series_id,
|
|
|
|
|
|
|
|
'display_id': display_id,
|
|
|
|
|
|
|
|
'title': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
|
|
|
|
|
|
|
|
'entries': entries
|
|
|
|
|
|
|
|
}
|
|
|
|