4 from .common
import InfoExtractor
5 from ..networking
.exceptions
import HTTPError
16 class ZattooPlatformBaseIE(InfoExtractor
):
17 _power_guide_hash
= None
20 return 'https://%s' % (self
._API
_HOST
if hasattr(self
, '_API_HOST') else self
._HOST
)
22 def _real_initialize(self
):
23 if not self
._power
_guide
_hash
:
24 self
.raise_login_required('An account is needed to access this media', method
='password')
26 def _perform_login(self
, username
, password
):
28 data
= self
._download
_json
(
29 f
'{self._host_url()}/zapi/v2/account/login', None, 'Logging in',
30 data
=urlencode_postdata({
35 'Referer': f
'{self._host_url()}/login',
36 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
38 except ExtractorError
as e
:
39 if isinstance(e
.cause
, HTTPError
) and e
.cause
.status
== 400:
41 'Unable to login: incorrect username and/or password',
45 self
._power
_guide
_hash
= data
['session']['power_guide_hash']
47 def _initialize_pre_login(self
):
48 session_token
= self
._download
_json
(
49 f
'{self._host_url()}/token.json', None, 'Downloading session token')['session_token']
51 # Will setup appropriate cookies
52 self
._request
_webpage
(
53 f
'{self._host_url()}/zapi/v3/session/hello', None,
54 'Opening session', data
=urlencode_postdata({
55 'uuid': str(uuid
.uuid4()),
57 'app_version': '1.8.2',
59 'client_app_token': session_token
,
62 def _extract_video_id_from_recording(self
, recid
):
63 playlist
= self
._download
_json
(
64 f
'{self._host_url()}/zapi/v2/playlist', recid
, 'Downloading playlist')
67 str(item
['program_id']) for item
in playlist
['recordings']
68 if item
.get('program_id') and str(item
.get('id')) == recid
)
69 except (StopIteration, KeyError):
70 raise ExtractorError('Could not extract video id from recording')
72 def _extract_cid(self
, video_id
, channel_name
):
73 channel_groups
= self
._download
_json
(
74 f
'{self._host_url()}/zapi/v2/cached/channels/{self._power_guide_hash}',
75 video_id
, 'Downloading channel list',
76 query
={'details': False})['channel_groups']
78 for chgrp
in channel_groups
:
79 channel_list
.extend(chgrp
['channels'])
82 chan
['cid'] for chan
in channel_list
83 if chan
.get('cid') and (
84 chan
.get('display_alias') == channel_name
85 or chan
.get('cid') == channel_name
))
87 raise ExtractorError('Could not extract channel id')
89 def _extract_cid_and_video_info(self
, video_id
):
90 data
= self
._download
_json
(
91 f
'{self._host_url()}/zapi/v2/cached/program/power_details/{self._power_guide_hash}',
93 'Downloading video information',
95 'program_ids': video_id
,
99 p
= data
['programs'][0]
104 'title': p
.get('t') or p
['et'],
105 'description': p
.get('d'),
106 'thumbnail': p
.get('i_url'),
107 'creator': p
.get('channel_name'),
108 'episode': p
.get('et'),
109 'episode_number': int_or_none(p
.get('e_no')),
110 'season_number': int_or_none(p
.get('s_no')),
111 'release_year': int_or_none(p
.get('year')),
112 'categories': try_get(p
, lambda x
: x
['c'], list),
113 'tags': try_get(p
, lambda x
: x
['g'], list),
116 return cid
, info_dict
118 def _extract_ondemand_info(self
, ondemand_id
):
120 @returns (ondemand_token, ondemand_type, info_dict)
122 data
= self
._download
_json
(
123 f
'{self._host_url()}/zapi/vod/movies/{ondemand_id}',
124 ondemand_id
, 'Downloading ondemand information')
127 'title': data
.get('title'),
128 'description': data
.get('description'),
129 'duration': int_or_none(data
.get('duration')),
130 'release_year': int_or_none(data
.get('year')),
131 'episode_number': int_or_none(data
.get('episode_number')),
132 'season_number': int_or_none(data
.get('season_number')),
133 'categories': try_get(data
, lambda x
: x
['categories'], list),
135 return data
['terms_catalog'][0]['terms'][0]['token'], data
['type'], info_dict
137 def _extract_formats(self
, cid
, video_id
, record_id
=None, ondemand_id
=None, ondemand_termtoken
=None, ondemand_type
=None, is_live
=False):
139 'https_watch_urls': True,
143 postdata_common
.update({'timeshift': 10800})
144 url
= f
'{self._host_url()}/zapi/watch/live/{cid}'
146 url
= f
'{self._host_url()}/zapi/watch/recording/{record_id}'
148 postdata_common
.update({
149 'teasable_id': ondemand_id
,
150 'term_token': ondemand_termtoken
,
151 'teasable_type': ondemand_type
,
153 url
= f
'{self._host_url()}/zapi/watch/vod/video'
155 url
= f
'{self._host_url()}/zapi/v3/watch/replay/{cid}/{video_id}'
158 for stream_type
in ('dash', 'hls7'):
159 postdata
= postdata_common
.copy()
160 postdata
['stream_type'] = stream_type
162 data
= self
._download
_json
(
163 url
, video_id
, f
'Downloading {stream_type.upper()} formats',
164 data
=urlencode_postdata(postdata
), fatal
=False)
168 watch_urls
= try_get(
169 data
, lambda x
: x
['stream']['watch_urls'], list)
173 for watch
in watch_urls
:
174 if not isinstance(watch
, dict):
176 watch_url
= url_or_none(watch
.get('url'))
179 audio_channel
= watch
.get('audio_channel')
180 preference
= 1 if audio_channel
== 'A' else None
181 format_id
= join_nonempty(stream_type
, watch
.get('maxrate'), audio_channel
)
182 if stream_type
.startswith('dash'):
183 this_formats
, subs
= self
._extract
_mpd
_formats
_and
_subtitles
(
184 watch_url
, video_id
, mpd_id
=format_id
, fatal
=False)
185 self
._merge
_subtitles
(subs
, target
=subtitles
)
186 elif stream_type
.startswith('hls'):
187 this_formats
, subs
= self
._extract
_m
3u8_formats
_and
_subtitles
(
188 watch_url
, video_id
, 'mp4',
189 entry_protocol
='m3u8_native', m3u8_id
=format_id
,
191 self
._merge
_subtitles
(subs
, target
=subtitles
)
192 elif stream_type
== 'hds':
193 this_formats
= self
._extract
_f
4m
_formats
(
194 watch_url
, video_id
, f4m_id
=format_id
, fatal
=False)
195 elif stream_type
== 'smooth_playready':
196 this_formats
= self
._extract
_ism
_formats
(
197 watch_url
, video_id
, ism_id
=format_id
, fatal
=False)
200 for this_format
in this_formats
:
201 this_format
['quality'] = preference
202 formats
.extend(this_formats
)
203 return formats
, subtitles
205 def _extract_video(self
, video_id
, record_id
=None):
206 cid
, info_dict
= self
._extract
_cid
_and
_video
_info
(video_id
)
207 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(cid
, video_id
, record_id
=record_id
)
210 def _extract_live(self
, channel_name
):
211 cid
= self
._extract
_cid
(channel_name
, channel_name
)
212 formats
, subtitles
= self
._extract
_formats
(cid
, cid
, is_live
=True)
215 'title': channel_name
,
218 'subtitles': subtitles
,
221 def _extract_record(self
, record_id
):
222 video_id
= self
._extract
_video
_id
_from
_recording
(record_id
)
223 cid
, info_dict
= self
._extract
_cid
_and
_video
_info
(video_id
)
224 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(cid
, video_id
, record_id
=record_id
)
227 def _extract_ondemand(self
, ondemand_id
):
228 ondemand_termtoken
, ondemand_type
, info_dict
= self
._extract
_ondemand
_info
(ondemand_id
)
229 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(
230 None, ondemand_id
, ondemand_id
=ondemand_id
,
231 ondemand_termtoken
=ondemand_termtoken
, ondemand_type
=ondemand_type
)
234 def _real_extract(self
, url
):
235 video_id
, record_id
= self
._match
_valid
_url
(url
).groups()
236 return getattr(self
, f
'_extract_{self._TYPE}')(video_id
or record_id
)
239 def _create_valid_url(host
, match
, qs
, base_re
=None):
240 match_base
= fr
'|{base_re}/(?P<vid1>{match})' if base_re
else '(?P<vid1>)'
241 return rf
'''(?x)https?://(?:www\.)?{re.escape(host)}/(?:
242 [^?#]+\?(?:[^#]+&)?{qs}=(?P<vid2>{match})
247 class ZattooBaseIE(ZattooPlatformBaseIE
):
248 _NETRC_MACHINE
= 'zattoo'
252 class ZattooIE(ZattooBaseIE
):
253 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
256 'url': 'https://zattoo.com/program/zdf/250170418',
260 'title': 'Markus Lanz',
261 'description': 'md5:e41cb1257de008ca62a73bb876ffa7fc',
262 'thumbnail': 're:http://images.zattic.com/cms/.+/format_480x360.jpg',
264 'release_year': 2022,
265 'episode': 'Folge 1655',
266 'categories': 'count:1',
269 'params': {'skip_download': 'm3u8'},
271 'url': 'https://zattoo.com/program/daserste/210177916',
272 'only_matching': True,
274 'url': 'https://zattoo.com/guide/german?channel=srf1&program=169860555',
275 'only_matching': True,
279 class ZattooLiveIE(ZattooBaseIE
):
280 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
283 'url': 'https://zattoo.com/channels/german?channel=srf_zwei',
284 'only_matching': True,
286 'url': 'https://zattoo.com/live/srf1',
287 'only_matching': True,
291 def suitable(cls
, url
):
292 return False if ZattooIE
.suitable(url
) else super().suitable(url
)
295 class ZattooMoviesIE(ZattooBaseIE
):
296 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'\w+', 'movie_id', 'vod/movies')
299 'url': 'https://zattoo.com/vod/movies/7521',
300 'only_matching': True,
302 'url': 'https://zattoo.com/ondemand?movie_id=7521&term_token=9f00f43183269484edde',
303 'only_matching': True,
307 class ZattooRecordingsIE(ZattooBaseIE
):
308 _VALID_URL
= _create_valid_url('zattoo.com', r
'\d+', 'recording')
311 'url': 'https://zattoo.com/recordings?recording=193615508',
312 'only_matching': True,
314 'url': 'https://zattoo.com/tc/ptc_recordings_all_recordings?recording=193615420',
315 'only_matching': True,
319 class NetPlusTVBaseIE(ZattooPlatformBaseIE
):
320 _NETRC_MACHINE
= 'netplus'
322 _API_HOST
= f
'www.{_HOST}'
325 class NetPlusTVIE(NetPlusTVBaseIE
):
326 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
329 'url': 'https://netplus.tv/program/daserste/210177916',
330 'only_matching': True,
332 'url': 'https://netplus.tv/guide/german?channel=srf1&program=169860555',
333 'only_matching': True,
337 class NetPlusTVLiveIE(NetPlusTVBaseIE
):
338 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
341 'url': 'https://netplus.tv/channels/german?channel=srf_zwei',
342 'only_matching': True,
344 'url': 'https://netplus.tv/live/srf1',
345 'only_matching': True,
349 def suitable(cls
, url
):
350 return False if NetPlusTVIE
.suitable(url
) else super().suitable(url
)
353 class NetPlusTVRecordingsIE(NetPlusTVBaseIE
):
354 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'\d+', 'recording')
357 'url': 'https://netplus.tv/recordings?recording=193615508',
358 'only_matching': True,
360 'url': 'https://netplus.tv/tc/ptc_recordings_all_recordings?recording=193615420',
361 'only_matching': True,
365 class MNetTVBaseIE(ZattooPlatformBaseIE
):
366 _NETRC_MACHINE
= 'mnettv'
367 _HOST
= 'tvplus.m-net.de'
370 class MNetTVIE(MNetTVBaseIE
):
371 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
374 'url': 'https://tvplus.m-net.de/program/daserste/210177916',
375 'only_matching': True,
377 'url': 'https://tvplus.m-net.de/guide/german?channel=srf1&program=169860555',
378 'only_matching': True,
382 class MNetTVLiveIE(MNetTVBaseIE
):
383 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
386 'url': 'https://tvplus.m-net.de/channels/german?channel=srf_zwei',
387 'only_matching': True,
389 'url': 'https://tvplus.m-net.de/live/srf1',
390 'only_matching': True,
394 def suitable(cls
, url
):
395 return False if MNetTVIE
.suitable(url
) else super().suitable(url
)
398 class MNetTVRecordingsIE(MNetTVBaseIE
):
399 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'\d+', 'recording')
402 'url': 'https://tvplus.m-net.de/recordings?recording=193615508',
403 'only_matching': True,
405 'url': 'https://tvplus.m-net.de/tc/ptc_recordings_all_recordings?recording=193615420',
406 'only_matching': True,
410 class WalyTVBaseIE(ZattooPlatformBaseIE
):
411 _NETRC_MACHINE
= 'walytv'
412 _HOST
= 'player.waly.tv'
415 class WalyTVIE(WalyTVBaseIE
):
416 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
419 'url': 'https://player.waly.tv/program/daserste/210177916',
420 'only_matching': True,
422 'url': 'https://player.waly.tv/guide/german?channel=srf1&program=169860555',
423 'only_matching': True,
427 class WalyTVLiveIE(WalyTVBaseIE
):
428 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
431 'url': 'https://player.waly.tv/channels/german?channel=srf_zwei',
432 'only_matching': True,
434 'url': 'https://player.waly.tv/live/srf1',
435 'only_matching': True,
439 def suitable(cls
, url
):
440 return False if WalyTVIE
.suitable(url
) else super().suitable(url
)
443 class WalyTVRecordingsIE(WalyTVBaseIE
):
444 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'\d+', 'recording')
447 'url': 'https://player.waly.tv/recordings?recording=193615508',
448 'only_matching': True,
450 'url': 'https://player.waly.tv/tc/ptc_recordings_all_recordings?recording=193615420',
451 'only_matching': True,
455 class BBVTVBaseIE(ZattooPlatformBaseIE
):
456 _NETRC_MACHINE
= 'bbvtv'
458 _API_HOST
= f
'www.{_HOST}'
461 class BBVTVIE(BBVTVBaseIE
):
462 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
465 'url': 'https://bbv-tv.net/program/daserste/210177916',
466 'only_matching': True,
468 'url': 'https://bbv-tv.net/guide/german?channel=srf1&program=169860555',
469 'only_matching': True,
473 class BBVTVLiveIE(BBVTVBaseIE
):
474 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
477 'url': 'https://bbv-tv.net/channels/german?channel=srf_zwei',
478 'only_matching': True,
480 'url': 'https://bbv-tv.net/live/srf1',
481 'only_matching': True,
485 def suitable(cls
, url
):
486 return False if BBVTVIE
.suitable(url
) else super().suitable(url
)
489 class BBVTVRecordingsIE(BBVTVBaseIE
):
490 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'\d+', 'recording')
493 'url': 'https://bbv-tv.net/recordings?recording=193615508',
494 'only_matching': True,
496 'url': 'https://bbv-tv.net/tc/ptc_recordings_all_recordings?recording=193615420',
497 'only_matching': True,
501 class VTXTVBaseIE(ZattooPlatformBaseIE
):
502 _NETRC_MACHINE
= 'vtxtv'
504 _API_HOST
= f
'www.{_HOST}'
507 class VTXTVIE(VTXTVBaseIE
):
508 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
511 'url': 'https://vtxtv.ch/program/daserste/210177916',
512 'only_matching': True,
514 'url': 'https://vtxtv.ch/guide/german?channel=srf1&program=169860555',
515 'only_matching': True,
519 class VTXTVLiveIE(VTXTVBaseIE
):
520 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
523 'url': 'https://vtxtv.ch/channels/german?channel=srf_zwei',
524 'only_matching': True,
526 'url': 'https://vtxtv.ch/live/srf1',
527 'only_matching': True,
531 def suitable(cls
, url
):
532 return False if VTXTVIE
.suitable(url
) else super().suitable(url
)
535 class VTXTVRecordingsIE(VTXTVBaseIE
):
536 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'\d+', 'recording')
539 'url': 'https://vtxtv.ch/recordings?recording=193615508',
540 'only_matching': True,
542 'url': 'https://vtxtv.ch/tc/ptc_recordings_all_recordings?recording=193615420',
543 'only_matching': True,
547 class GlattvisionTVBaseIE(ZattooPlatformBaseIE
):
548 _NETRC_MACHINE
= 'glattvisiontv'
549 _HOST
= 'iptv.glattvision.ch'
552 class GlattvisionTVIE(GlattvisionTVBaseIE
):
553 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
556 'url': 'https://iptv.glattvision.ch/program/daserste/210177916',
557 'only_matching': True,
559 'url': 'https://iptv.glattvision.ch/guide/german?channel=srf1&program=169860555',
560 'only_matching': True,
564 class GlattvisionTVLiveIE(GlattvisionTVBaseIE
):
565 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
568 'url': 'https://iptv.glattvision.ch/channels/german?channel=srf_zwei',
569 'only_matching': True,
571 'url': 'https://iptv.glattvision.ch/live/srf1',
572 'only_matching': True,
576 def suitable(cls
, url
):
577 return False if GlattvisionTVIE
.suitable(url
) else super().suitable(url
)
580 class GlattvisionTVRecordingsIE(GlattvisionTVBaseIE
):
581 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'\d+', 'recording')
584 'url': 'https://iptv.glattvision.ch/recordings?recording=193615508',
585 'only_matching': True,
587 'url': 'https://iptv.glattvision.ch/tc/ptc_recordings_all_recordings?recording=193615420',
588 'only_matching': True,
592 class SAKTVBaseIE(ZattooPlatformBaseIE
):
593 _NETRC_MACHINE
= 'saktv'
595 _API_HOST
= f
'www.{_HOST}'
598 class SAKTVIE(SAKTVBaseIE
):
599 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
602 'url': 'https://saktv.ch/program/daserste/210177916',
603 'only_matching': True,
605 'url': 'https://saktv.ch/guide/german?channel=srf1&program=169860555',
606 'only_matching': True,
610 class SAKTVLiveIE(SAKTVBaseIE
):
611 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
614 'url': 'https://saktv.ch/channels/german?channel=srf_zwei',
615 'only_matching': True,
617 'url': 'https://saktv.ch/live/srf1',
618 'only_matching': True,
622 def suitable(cls
, url
):
623 return False if SAKTVIE
.suitable(url
) else super().suitable(url
)
626 class SAKTVRecordingsIE(SAKTVBaseIE
):
627 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'\d+', 'recording')
630 'url': 'https://saktv.ch/recordings?recording=193615508',
631 'only_matching': True,
633 'url': 'https://saktv.ch/tc/ptc_recordings_all_recordings?recording=193615420',
634 'only_matching': True,
638 class EWETVBaseIE(ZattooPlatformBaseIE
):
639 _NETRC_MACHINE
= 'ewetv'
640 _HOST
= 'tvonline.ewe.de'
643 class EWETVIE(EWETVBaseIE
):
644 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
647 'url': 'https://tvonline.ewe.de/program/daserste/210177916',
648 'only_matching': True,
650 'url': 'https://tvonline.ewe.de/guide/german?channel=srf1&program=169860555',
651 'only_matching': True,
655 class EWETVLiveIE(EWETVBaseIE
):
656 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
659 'url': 'https://tvonline.ewe.de/channels/german?channel=srf_zwei',
660 'only_matching': True,
662 'url': 'https://tvonline.ewe.de/live/srf1',
663 'only_matching': True,
667 def suitable(cls
, url
):
668 return False if EWETVIE
.suitable(url
) else super().suitable(url
)
671 class EWETVRecordingsIE(EWETVBaseIE
):
672 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'\d+', 'recording')
675 'url': 'https://tvonline.ewe.de/recordings?recording=193615508',
676 'only_matching': True,
678 'url': 'https://tvonline.ewe.de/tc/ptc_recordings_all_recordings?recording=193615420',
679 'only_matching': True,
683 class QuantumTVBaseIE(ZattooPlatformBaseIE
):
684 _NETRC_MACHINE
= 'quantumtv'
685 _HOST
= 'quantum-tv.com'
686 _API_HOST
= f
'www.{_HOST}'
689 class QuantumTVIE(QuantumTVBaseIE
):
690 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
693 'url': 'https://quantum-tv.com/program/daserste/210177916',
694 'only_matching': True,
696 'url': 'https://quantum-tv.com/guide/german?channel=srf1&program=169860555',
697 'only_matching': True,
701 class QuantumTVLiveIE(QuantumTVBaseIE
):
702 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
705 'url': 'https://quantum-tv.com/channels/german?channel=srf_zwei',
706 'only_matching': True,
708 'url': 'https://quantum-tv.com/live/srf1',
709 'only_matching': True,
713 def suitable(cls
, url
):
714 return False if QuantumTVIE
.suitable(url
) else super().suitable(url
)
717 class QuantumTVRecordingsIE(QuantumTVBaseIE
):
718 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'\d+', 'recording')
721 'url': 'https://quantum-tv.com/recordings?recording=193615508',
722 'only_matching': True,
724 'url': 'https://quantum-tv.com/tc/ptc_recordings_all_recordings?recording=193615420',
725 'only_matching': True,
729 class OsnatelTVBaseIE(ZattooPlatformBaseIE
):
730 _NETRC_MACHINE
= 'osnateltv'
731 _HOST
= 'tvonline.osnatel.de'
734 class OsnatelTVIE(OsnatelTVBaseIE
):
735 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
738 'url': 'https://tvonline.osnatel.de/program/daserste/210177916',
739 'only_matching': True,
741 'url': 'https://tvonline.osnatel.de/guide/german?channel=srf1&program=169860555',
742 'only_matching': True,
746 class OsnatelTVLiveIE(OsnatelTVBaseIE
):
747 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
750 'url': 'https://tvonline.osnatel.de/channels/german?channel=srf_zwei',
751 'only_matching': True,
753 'url': 'https://tvonline.osnatel.de/live/srf1',
754 'only_matching': True,
758 def suitable(cls
, url
):
759 return False if OsnatelTVIE
.suitable(url
) else super().suitable(url
)
762 class OsnatelTVRecordingsIE(OsnatelTVBaseIE
):
763 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'\d+', 'recording')
766 'url': 'https://tvonline.osnatel.de/recordings?recording=193615508',
767 'only_matching': True,
769 'url': 'https://tvonline.osnatel.de/tc/ptc_recordings_all_recordings?recording=193615420',
770 'only_matching': True,
774 class EinsUndEinsTVBaseIE(ZattooPlatformBaseIE
):
775 _NETRC_MACHINE
= '1und1tv'
777 _API_HOST
= f
'www.{_HOST}'
780 class EinsUndEinsTVIE(EinsUndEinsTVBaseIE
):
781 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
784 'url': 'https://1und1.tv/program/daserste/210177916',
785 'only_matching': True,
787 'url': 'https://1und1.tv/guide/german?channel=srf1&program=169860555',
788 'only_matching': True,
792 class EinsUndEinsTVLiveIE(EinsUndEinsTVBaseIE
):
793 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
796 'url': 'https://1und1.tv/channels/german?channel=srf_zwei',
797 'only_matching': True,
799 'url': 'https://1und1.tv/live/srf1',
800 'only_matching': True,
804 def suitable(cls
, url
):
805 return False if EinsUndEinsTVIE
.suitable(url
) else super().suitable(url
)
808 class EinsUndEinsTVRecordingsIE(EinsUndEinsTVBaseIE
):
809 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'\d+', 'recording')
812 'url': 'https://1und1.tv/recordings?recording=193615508',
813 'only_matching': True,
815 'url': 'https://1und1.tv/tc/ptc_recordings_all_recordings?recording=193615420',
816 'only_matching': True,
820 class SaltTVBaseIE(ZattooPlatformBaseIE
):
821 _NETRC_MACHINE
= 'salttv'
825 class SaltTVIE(SaltTVBaseIE
):
826 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
829 'url': 'https://tv.salt.ch/program/daserste/210177916',
830 'only_matching': True,
832 'url': 'https://tv.salt.ch/guide/german?channel=srf1&program=169860555',
833 'only_matching': True,
837 class SaltTVLiveIE(SaltTVBaseIE
):
838 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
841 'url': 'https://tv.salt.ch/channels/german?channel=srf_zwei',
842 'only_matching': True,
844 'url': 'https://tv.salt.ch/live/srf1',
845 'only_matching': True,
849 def suitable(cls
, url
):
850 return False if SaltTVIE
.suitable(url
) else super().suitable(url
)
853 class SaltTVRecordingsIE(SaltTVBaseIE
):
854 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'\d+', 'recording')
857 'url': 'https://tv.salt.ch/recordings?recording=193615508',
858 'only_matching': True,
860 'url': 'https://tv.salt.ch/tc/ptc_recordings_all_recordings?recording=193615420',
861 'only_matching': True,