diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py index 6e8fc3961..efcca1343 100644 --- a/youtube_dl/extractor/extractors.py +++ b/youtube_dl/extractor/extractors.py @@ -922,6 +922,7 @@ from .playfm import PlayFMIE from .playplustv import PlayPlusTVIE from .plays import PlaysTVIE from .playstuff import PlayStuffIE +from .playsuisse import PlaySuisseIE from .playtvak import PlaytvakIE from .playvid import PlayvidIE from .playwire import PlaywireIE diff --git a/youtube_dl/extractor/playsuisse.py b/youtube_dl/extractor/playsuisse.py new file mode 100644 index 000000000..bc49fb549 --- /dev/null +++ b/youtube_dl/extractor/playsuisse.py @@ -0,0 +1,299 @@ +# coding: utf-8 +from __future__ import unicode_literals +import json + +from .common import InfoExtractor +from ..utils import std_headers + + +class PlaySuisseIE(InfoExtractor): + _MEDIA_URL = 'https://4bbepzm4ef.execute-api.eu-central-1.amazonaws.com/prod/graphql' + _VALID_URL = r'https?://(?:www\.)?playsuisse\.ch/watch/(?P[0-9]+)' + _TESTS = [ + { + 'url': 'https://www.playsuisse.ch/watch/763211/0', + 'md5': '0d716b7a16c3e6ab784ef817ee9a20c1', + 'info_dict': { + 'id': '763211', + 'ext': 'mp4', + 'title': 'Wilder S01E01 - Knochen', + 'description': 'md5:8ea7a8076ba000cd9e8bc132fd0afdd8' + } + }, + { + 'url': 'https://www.playsuisse.ch/watch/808675/0', + 'md5': '7aa043e69fea5044db2da8bb58bca239', + 'info_dict': { + 'id': '808675', + 'ext': 'mp4', + 'title': 'Der Läufer', + 'description': 'md5:' + } + }, + { + 'url': 'https://www.playsuisse.ch/watch/817913/0', + 'md5': '50721c46ca0b3a9836eb61ecb0ed7097', + 'info_dict': { + 'id': '42', + 'ext': 'mp4', + 'title': 'Nr. 47 S01E01 - Die Einweihungsparty', + 'description': 'md5:' + } + } + ] + + def _get_media_data(self, media_id): + locale = std_headers.get('locale', 'de').strip() + # TODO find out why the locale has no effect in request + response = self._download_json( + self._MEDIA_URL, + media_id, + data=json.dumps({ + 'operationName': 'AssetWatch', + 'query': self._GRAPHQL_QUERY, + 'variables': { + "assetId": media_id + } + }).encode('utf-8'), + headers={'Content-Type': 'application/json', 'locale': locale}) + + return response['data']['asset'] + + def _real_extract(self, url): + media_id, = self._VALID_URL_RE.match(url).groups() + media_data = self._get_media_data(media_id) + + def _get_title(data): + if data.get('seriesName'): + return '{} S{:02}E{:02} - {}'.format( + data['seriesName'], + int(data['seasonNumber']), + int(data['episodeNumber']), + data['name']) + else: + return data['name'] + + title = _get_title(media_data) + description = media_data['description'] + thumbnails = [ + { + 'id': thumb['id'], + 'url': thumb['url'] + } + for key, thumb in media_data.items() + if key.startswith('thumbnail') and thumb is not None + ] + + formats = [] + + for media in media_data['medias']: + # NOTE subtitle info is included in the m3u8 file, but it's not supported by ytdl + # https://github.com/ytdl-org/youtube-dl/issues/6106 + if media['type'] == 'HLS': + formats.extend(self._extract_m3u8_formats( + media['url'], + media_id, + 'mp4', + 'm3u8_native', + m3u8_id="HLS", + fatal=False)) + + # NOTE seems to be 404 for all tested media + elif media['type'] == 'DASH' and False: + formats.extend(self._extract_mpd_formats( + media['url'], + media_id, + mpd_id='dash', + fatal=False + )) + + return { + 'id': media_id, + 'title': title, + 'description': description, + 'thumbnails': thumbnails, + 'formats': formats, + } + + _GRAPHQL_QUERY = '''\ +query AssetWatch($assetId: ID!) { + asset(assetId: $assetId) { + ...Asset + __typename + } +} +fragment Asset on Asset { + ...AssetDetails + episodes { + ...AssetDetails + __typename + } + __typename +} +fragment AssetDetails on Asset { + audioLanguages + awards + bu + contentCategories + contentCodes + contentTypes + contractType + countries + creators + creditsTimeInSecs + description + descriptionLong + directors + downloadable + duration + editorialContentCategoriesDatalab { + id + title + __typename + } + editorialContentMetaCategoriesDatalab { + id + title + __typename + } + endDate + episodeNumber + episodesInSequence + externalId + firstEpisodeDuration + id + image16x9 { + ...ImageDetails + __typename + } + image2x3 { + ...ImageDetails + __typename + } + image16x9WithTitle { + ...ImageDetails + __typename + } + image2x3WithTitle { + ...ImageDetails + __typename + } + mainCast + name + nextEpisode { + id + episodeNumber + seasonNumber + numberOfEpisodesInSeason + image16x9 { + ...ImageDetails + __typename + } + __typename + } + numberOfSeasons + otherKeyPeople + parentalRating + popularity + premium + presenters + primaryLanguage + productionCompanies + productionCountries + provider + ratings + regions + restrictions + seasons { + seasonNumber + assetIds + __typename + } + seasonNumber + seriesId + seriesName + nextEpisode { + ...NextEpisodeDetails + __typename + } + parentId + startDate + subtitleLanguages + tagline + targetAudience + themes + thumbnail16x9 { + ...ImageDetails + __typename + } + thumbnail2x3 { + ...ImageDetails + __typename + } + thumbnail16x9WithTitle { + ...ImageDetails + __typename + } + thumbnail2x3WithTitle { + ...ImageDetails + __typename + } + type + writers + year + medias { + ...MediaDetails + __typename + } + trailerMedias { + ...MediaDetails + __typename + } + sponsors { + ...SponsorDetails + __typename + } + sponsorEndDate + __typename +} +fragment ImageDetails on Image { + id + url + alt + __typename +} +fragment MediaDetails on Media { + id + type + url + duration + __typename +} +fragment SponsorDetails on Sponsor { + id + name + description + type + externalId + image16x9 { + ...ImageDetails + __typename + } + thumbnail16x9 { + ...ImageDetails + __typename + } + __typename +} +fragment NextEpisodeDetails on NextEpisode { + id + episodeNumber + seasonNumber + numberOfEpisodesInSeason + image16x9 { + ...ImageDetails + __typename + } + __typename +} +'''