]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/playsuisse.py
[extractor/playsuisse] Add extractor (#845)
[yt-dlp.git] / yt_dlp / extractor / playsuisse.py
CommitLineData
ee164987
SB
1import json
2
3from .common import InfoExtractor
4from ..utils import int_or_none, traverse_obj
5
6
7class PlaySuisseIE(InfoExtractor):
8 _VALID_URL = r'https?://(?:www\.)?playsuisse\.ch/watch/(?P<id>[0-9]+)'
9 _TESTS = [
10 {
11 'url': 'https://www.playsuisse.ch/watch/763211/0',
12 'md5': '82df2a470b2dfa60c2d33772a8a60cf8',
13 'info_dict': {
14 'id': '763211',
15 'ext': 'mp4',
16 'title': 'Knochen',
17 'description': 'md5:8ea7a8076ba000cd9e8bc132fd0afdd8',
18 'duration': 3344,
19 'series': 'Wilder',
20 'season': 'Season 1',
21 'season_number': 1,
22 'episode': 'Knochen',
23 'episode_number': 1,
24 'thumbnail': 'md5:9260abe0c0ec9b69914d0a10d54c5878'
25 }
26 },
27 {
28 'url': 'https://www.playsuisse.ch/watch/808675/0',
29 'md5': '818b94c1d2d7c4beef953f12cb8f3e75',
30 'info_dict': {
31 'id': '808675',
32 'ext': 'mp4',
33 'title': 'Der Läufer',
34 'description': 'md5:9f61265c7e6dcc3e046137a792b275fd',
35 'duration': 5280,
36 'episode': 'Der Läufer',
37 'thumbnail': 'md5:44af7d65ee02bbba4576b131868bb783'
38 }
39 },
40 {
41 'url': 'https://www.playsuisse.ch/watch/817193/0',
42 'md5': '1d6c066f92cd7fffd8b28a53526d6b59',
43 'info_dict': {
44 'id': '817193',
45 'ext': 'mp4',
46 'title': 'Die Einweihungsparty',
47 'description': 'md5:91ebf04d3a42cb3ab70666acf750a930',
48 'duration': 1380,
49 'series': 'Nr. 47',
50 'season': 'Season 1',
51 'season_number': 1,
52 'episode': 'Die Einweihungsparty',
53 'episode_number': 1,
54 'thumbnail': 'md5:637585fb106e3a4bcd991958924c7e44'
55 }
56 }
57 ]
58
59 _GRAPHQL_QUERY = '''
60 query AssetWatch($assetId: ID!) {
61 assetV2(id: $assetId) {
62 ...Asset
63 episodes {
64 ...Asset
65 }
66 }
67 }
68 fragment Asset on AssetV2 {
69 id
70 name
71 description
72 duration
73 episodeNumber
74 seasonNumber
75 seriesName
76 medias {
77 type
78 url
79 }
80 thumbnail16x9 {
81 ...ImageDetails
82 }
83 thumbnail2x3 {
84 ...ImageDetails
85 }
86 thumbnail16x9WithTitle {
87 ...ImageDetails
88 }
89 thumbnail2x3WithTitle {
90 ...ImageDetails
91 }
92 }
93 fragment ImageDetails on AssetImage {
94 id
95 url
96 }'''
97
98 def _get_media_data(self, media_id):
99 # NOTE In the web app, the "locale" header is used to switch between languages,
100 # However this doesn't seem to take effect when passing the header here.
101 response = self._download_json(
102 'https://4bbepzm4ef.execute-api.eu-central-1.amazonaws.com/prod/graphql',
103 media_id, data=json.dumps({
104 'operationName': 'AssetWatch',
105 'query': self._GRAPHQL_QUERY,
106 'variables': {'assetId': media_id}
107 }).encode('utf-8'),
108 headers={'Content-Type': 'application/json', 'locale': 'de'})
109
110 return response['data']['assetV2']
111
112 def _real_extract(self, url):
113 media_id = self._match_id(url)
114 media_data = self._get_media_data(media_id)
115 info = self._extract_single(media_data)
116 if media_data.get('episodes'):
117 info.update({
118 '_type': 'playlist',
119 'entries': map(self._extract_single, media_data['episodes']),
120 })
121 return info
122
123 def _extract_single(self, media_data):
124 thumbnails = traverse_obj(media_data, lambda k, _: k.startswith('thumbnail'))
125
126 formats, subtitles = [], {}
127 for media in traverse_obj(media_data, 'medias', default=[]):
128 if not media.get('url') or media.get('type') != 'HLS':
129 continue
130 f, subs = self._extract_m3u8_formats_and_subtitles(
131 media['url'], media_data['id'], 'mp4', m3u8_id='HLS', fatal=False)
132 formats.extend(f)
133 self._merge_subtitles(subs, target=subtitles)
134
135 return {
136 'id': media_data['id'],
137 'title': media_data.get('name'),
138 'description': media_data.get('description'),
139 'thumbnails': thumbnails,
140 'duration': int_or_none(media_data.get('duration')),
141 'formats': formats,
142 'subtitles': subtitles,
143 'series': media_data.get('seriesName'),
144 'season_number': int_or_none(media_data.get('seasonNumber')),
145 'episode': media_data.get('name'),
146 'episode_number': int_or_none(media_data.get('episodeNumber')),
147 }