4 from .common
import InfoExtractor
5 from ..compat
import compat_str
6 from ..networking
.exceptions
import HTTPError
17 class ZattooPlatformBaseIE(InfoExtractor
):
18 _power_guide_hash
= None
21 return 'https://%s' % (self
._API
_HOST
if hasattr(self
, '_API_HOST') else self
._HOST
)
23 def _real_initialize(self
):
24 if not self
._power
_guide
_hash
:
25 self
.raise_login_required('An account is needed to access this media', method
='password')
27 def _perform_login(self
, username
, password
):
29 data
= self
._download
_json
(
30 '%s/zapi/v2/account/login' % self
._host
_url
(), None, 'Logging in',
31 data
=urlencode_postdata({
36 'Referer': '%s/login' % self
._host
_url
(),
37 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
39 except ExtractorError
as e
:
40 if isinstance(e
.cause
, HTTPError
) and e
.cause
.status
== 400:
42 'Unable to login: incorrect username and/or password',
46 self
._power
_guide
_hash
= data
['session']['power_guide_hash']
48 def _initialize_pre_login(self
):
49 session_token
= self
._download
_json
(
50 f
'{self._host_url()}/token.json', None, 'Downloading session token')['session_token']
52 # Will setup appropriate cookies
53 self
._request
_webpage
(
54 '%s/zapi/v3/session/hello' % self
._host
_url
(), None,
55 'Opening session', data
=urlencode_postdata({
56 'uuid': compat_str(uuid4()),
58 'app_version': '1.8.2',
60 'client_app_token': session_token
,
63 def _extract_video_id_from_recording(self
, recid
):
64 playlist
= self
._download
_json
(
65 f
'{self._host_url()}/zapi/v2/playlist', recid
, 'Downloading playlist')
68 str(item
['program_id']) for item
in playlist
['recordings']
69 if item
.get('program_id') and str(item
.get('id')) == recid
)
70 except (StopIteration, KeyError):
71 raise ExtractorError('Could not extract video id from recording')
73 def _extract_cid(self
, video_id
, channel_name
):
74 channel_groups
= self
._download
_json
(
75 '%s/zapi/v2/cached/channels/%s' % (self
._host
_url
(),
76 self
._power
_guide
_hash
),
77 video_id
, 'Downloading channel list',
78 query
={'details': False}
)['channel_groups']
80 for chgrp
in channel_groups
:
81 channel_list
.extend(chgrp
['channels'])
84 chan
['cid'] for chan
in channel_list
85 if chan
.get('cid') and (
86 chan
.get('display_alias') == channel_name
87 or chan
.get('cid') == channel_name
))
89 raise ExtractorError('Could not extract channel id')
91 def _extract_cid_and_video_info(self
, video_id
):
92 data
= self
._download
_json
(
93 '%s/zapi/v2/cached/program/power_details/%s' % (
94 self
._host
_url
(), self
._power
_guide
_hash
),
96 'Downloading video information',
98 'program_ids': video_id
,
102 p
= data
['programs'][0]
107 'title': p
.get('t') or p
['et'],
108 'description': p
.get('d'),
109 'thumbnail': p
.get('i_url'),
110 'creator': p
.get('channel_name'),
111 'episode': p
.get('et'),
112 'episode_number': int_or_none(p
.get('e_no')),
113 'season_number': int_or_none(p
.get('s_no')),
114 'release_year': int_or_none(p
.get('year')),
115 'categories': try_get(p
, lambda x
: x
['c'], list),
116 'tags': try_get(p
, lambda x
: x
['g'], list)
119 return cid
, info_dict
121 def _extract_ondemand_info(self
, ondemand_id
):
123 @returns (ondemand_token, ondemand_type, info_dict)
125 data
= self
._download
_json
(
126 '%s/zapi/vod/movies/%s' % (self
._host
_url
(), ondemand_id
),
127 ondemand_id
, 'Downloading ondemand information')
130 'title': data
.get('title'),
131 'description': data
.get('description'),
132 'duration': int_or_none(data
.get('duration')),
133 'release_year': int_or_none(data
.get('year')),
134 'episode_number': int_or_none(data
.get('episode_number')),
135 'season_number': int_or_none(data
.get('season_number')),
136 'categories': try_get(data
, lambda x
: x
['categories'], list),
138 return data
['terms_catalog'][0]['terms'][0]['token'], data
['type'], info_dict
140 def _extract_formats(self
, cid
, video_id
, record_id
=None, ondemand_id
=None, ondemand_termtoken
=None, ondemand_type
=None, is_live
=False):
142 'https_watch_urls': True,
146 postdata_common
.update({'timeshift': 10800}
)
147 url
= '%s/zapi/watch/live/%s' % (self
._host
_url
(), cid
)
149 url
= '%s/zapi/watch/recording/%s' % (self
._host
_url
(), record_id
)
151 postdata_common
.update({
152 'teasable_id': ondemand_id
,
153 'term_token': ondemand_termtoken
,
154 'teasable_type': ondemand_type
156 url
= '%s/zapi/watch/vod/video' % self
._host
_url
()
158 url
= '%s/zapi/v3/watch/replay/%s/%s' % (self
._host
_url
(), cid
, video_id
)
161 for stream_type
in ('dash', 'hls7'):
162 postdata
= postdata_common
.copy()
163 postdata
['stream_type'] = stream_type
165 data
= self
._download
_json
(
166 url
, video_id
, 'Downloading %s formats' % stream_type
.upper(),
167 data
=urlencode_postdata(postdata
), fatal
=False)
171 watch_urls
= try_get(
172 data
, lambda x
: x
['stream']['watch_urls'], list)
176 for watch
in watch_urls
:
177 if not isinstance(watch
, dict):
179 watch_url
= url_or_none(watch
.get('url'))
182 audio_channel
= watch
.get('audio_channel')
183 preference
= 1 if audio_channel
== 'A' else None
184 format_id
= join_nonempty(stream_type
, watch
.get('maxrate'), audio_channel
)
185 if stream_type
.startswith('dash'):
186 this_formats
, subs
= self
._extract
_mpd
_formats
_and
_subtitles
(
187 watch_url
, video_id
, mpd_id
=format_id
, fatal
=False)
188 self
._merge
_subtitles
(subs
, target
=subtitles
)
189 elif stream_type
.startswith('hls'):
190 this_formats
, subs
= self
._extract
_m
3u8_formats
_and
_subtitles
(
191 watch_url
, video_id
, 'mp4',
192 entry_protocol
='m3u8_native', m3u8_id
=format_id
,
194 self
._merge
_subtitles
(subs
, target
=subtitles
)
195 elif stream_type
== 'hds':
196 this_formats
= self
._extract
_f
4m
_formats
(
197 watch_url
, video_id
, f4m_id
=format_id
, fatal
=False)
198 elif stream_type
== 'smooth_playready':
199 this_formats
= self
._extract
_ism
_formats
(
200 watch_url
, video_id
, ism_id
=format_id
, fatal
=False)
203 for this_format
in this_formats
:
204 this_format
['quality'] = preference
205 formats
.extend(this_formats
)
206 return formats
, subtitles
208 def _extract_video(self
, video_id
, record_id
=None):
209 cid
, info_dict
= self
._extract
_cid
_and
_video
_info
(video_id
)
210 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(cid
, video_id
, record_id
=record_id
)
213 def _extract_live(self
, channel_name
):
214 cid
= self
._extract
_cid
(channel_name
, channel_name
)
215 formats
, subtitles
= self
._extract
_formats
(cid
, cid
, is_live
=True)
218 'title': channel_name
,
221 'subtitles': subtitles
224 def _extract_record(self
, record_id
):
225 video_id
= self
._extract
_video
_id
_from
_recording
(record_id
)
226 cid
, info_dict
= self
._extract
_cid
_and
_video
_info
(video_id
)
227 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(cid
, video_id
, record_id
=record_id
)
230 def _extract_ondemand(self
, ondemand_id
):
231 ondemand_termtoken
, ondemand_type
, info_dict
= self
._extract
_ondemand
_info
(ondemand_id
)
232 info_dict
['formats'], info_dict
['subtitles'] = self
._extract
_formats
(
233 None, ondemand_id
, ondemand_id
=ondemand_id
,
234 ondemand_termtoken
=ondemand_termtoken
, ondemand_type
=ondemand_type
)
237 def _real_extract(self
, url
):
238 video_id
, record_id
= self
._match
_valid
_url
(url
).groups()
239 return getattr(self
, f
'_extract_{self._TYPE}')(video_id
or record_id
)
242 def _create_valid_url(host
, match
, qs
, base_re
=None):
243 match_base
= fr
'|{base_re}/(?P<vid1>{match})' if base_re
else '(?P<vid1>)'
244 return rf
'''(?x)https?://(?:www\.)?{re.escape(host)}/(?:
245 [^?#]+\?(?:[^#]+&)?{qs}=(?P<vid2>{match})
250 class ZattooBaseIE(ZattooPlatformBaseIE
):
251 _NETRC_MACHINE
= 'zattoo'
255 class ZattooIE(ZattooBaseIE
):
256 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
259 'url': 'https://zattoo.com/program/zdf/250170418',
263 'title': 'Markus Lanz',
264 'description': 'md5:e41cb1257de008ca62a73bb876ffa7fc',
265 'thumbnail': 're:http://images.zattic.com/cms/.+/format_480x360.jpg',
267 'release_year': 2022,
268 'episode': 'Folge 1655',
269 'categories': 'count:1',
272 'params': {'skip_download': 'm3u8'}
274 'url': 'https://zattoo.com/program/daserste/210177916',
275 'only_matching': True,
277 'url': 'https://zattoo.com/guide/german?channel=srf1&program=169860555',
278 'only_matching': True,
282 class ZattooLiveIE(ZattooBaseIE
):
283 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
286 'url': 'https://zattoo.com/channels/german?channel=srf_zwei',
287 'only_matching': True,
289 'url': 'https://zattoo.com/live/srf1',
290 'only_matching': True,
294 def suitable(cls
, url
):
295 return False if ZattooIE
.suitable(url
) else super().suitable(url
)
298 class ZattooMoviesIE(ZattooBaseIE
):
299 _VALID_URL
= _create_valid_url(ZattooBaseIE
._HOST
, r
'\w+', 'movie_id', 'vod/movies')
302 'url': 'https://zattoo.com/vod/movies/7521',
303 'only_matching': True,
305 'url': 'https://zattoo.com/ondemand?movie_id=7521&term_token=9f00f43183269484edde',
306 'only_matching': True,
310 class ZattooRecordingsIE(ZattooBaseIE
):
311 _VALID_URL
= _create_valid_url('zattoo.com', r
'\d+', 'recording')
314 'url': 'https://zattoo.com/recordings?recording=193615508',
315 'only_matching': True,
317 'url': 'https://zattoo.com/tc/ptc_recordings_all_recordings?recording=193615420',
318 'only_matching': True,
322 class NetPlusTVBaseIE(ZattooPlatformBaseIE
):
323 _NETRC_MACHINE
= 'netplus'
325 _API_HOST
= 'www.%s' % _HOST
328 class NetPlusTVIE(NetPlusTVBaseIE
):
329 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
332 'url': 'https://netplus.tv/program/daserste/210177916',
333 'only_matching': True,
335 'url': 'https://netplus.tv/guide/german?channel=srf1&program=169860555',
336 'only_matching': True,
340 class NetPlusTVLiveIE(NetPlusTVBaseIE
):
341 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
344 'url': 'https://netplus.tv/channels/german?channel=srf_zwei',
345 'only_matching': True,
347 'url': 'https://netplus.tv/live/srf1',
348 'only_matching': True,
352 def suitable(cls
, url
):
353 return False if NetPlusTVIE
.suitable(url
) else super().suitable(url
)
356 class NetPlusTVRecordingsIE(NetPlusTVBaseIE
):
357 _VALID_URL
= _create_valid_url(NetPlusTVBaseIE
._HOST
, r
'\d+', 'recording')
360 'url': 'https://netplus.tv/recordings?recording=193615508',
361 'only_matching': True,
363 'url': 'https://netplus.tv/tc/ptc_recordings_all_recordings?recording=193615420',
364 'only_matching': True,
368 class MNetTVBaseIE(ZattooPlatformBaseIE
):
369 _NETRC_MACHINE
= 'mnettv'
370 _HOST
= 'tvplus.m-net.de'
373 class MNetTVIE(MNetTVBaseIE
):
374 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
377 'url': 'https://tvplus.m-net.de/program/daserste/210177916',
378 'only_matching': True,
380 'url': 'https://tvplus.m-net.de/guide/german?channel=srf1&program=169860555',
381 'only_matching': True,
385 class MNetTVLiveIE(MNetTVBaseIE
):
386 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
389 'url': 'https://tvplus.m-net.de/channels/german?channel=srf_zwei',
390 'only_matching': True,
392 'url': 'https://tvplus.m-net.de/live/srf1',
393 'only_matching': True,
397 def suitable(cls
, url
):
398 return False if MNetTVIE
.suitable(url
) else super().suitable(url
)
401 class MNetTVRecordingsIE(MNetTVBaseIE
):
402 _VALID_URL
= _create_valid_url(MNetTVBaseIE
._HOST
, r
'\d+', 'recording')
405 'url': 'https://tvplus.m-net.de/recordings?recording=193615508',
406 'only_matching': True,
408 'url': 'https://tvplus.m-net.de/tc/ptc_recordings_all_recordings?recording=193615420',
409 'only_matching': True,
413 class WalyTVBaseIE(ZattooPlatformBaseIE
):
414 _NETRC_MACHINE
= 'walytv'
415 _HOST
= 'player.waly.tv'
418 class WalyTVIE(WalyTVBaseIE
):
419 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
422 'url': 'https://player.waly.tv/program/daserste/210177916',
423 'only_matching': True,
425 'url': 'https://player.waly.tv/guide/german?channel=srf1&program=169860555',
426 'only_matching': True,
430 class WalyTVLiveIE(WalyTVBaseIE
):
431 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
434 'url': 'https://player.waly.tv/channels/german?channel=srf_zwei',
435 'only_matching': True,
437 'url': 'https://player.waly.tv/live/srf1',
438 'only_matching': True,
442 def suitable(cls
, url
):
443 return False if WalyTVIE
.suitable(url
) else super().suitable(url
)
446 class WalyTVRecordingsIE(WalyTVBaseIE
):
447 _VALID_URL
= _create_valid_url(WalyTVBaseIE
._HOST
, r
'\d+', 'recording')
450 'url': 'https://player.waly.tv/recordings?recording=193615508',
451 'only_matching': True,
453 'url': 'https://player.waly.tv/tc/ptc_recordings_all_recordings?recording=193615420',
454 'only_matching': True,
458 class BBVTVBaseIE(ZattooPlatformBaseIE
):
459 _NETRC_MACHINE
= 'bbvtv'
461 _API_HOST
= 'www.%s' % _HOST
464 class BBVTVIE(BBVTVBaseIE
):
465 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
468 'url': 'https://bbv-tv.net/program/daserste/210177916',
469 'only_matching': True,
471 'url': 'https://bbv-tv.net/guide/german?channel=srf1&program=169860555',
472 'only_matching': True,
476 class BBVTVLiveIE(BBVTVBaseIE
):
477 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
480 'url': 'https://bbv-tv.net/channels/german?channel=srf_zwei',
481 'only_matching': True,
483 'url': 'https://bbv-tv.net/live/srf1',
484 'only_matching': True,
488 def suitable(cls
, url
):
489 return False if BBVTVIE
.suitable(url
) else super().suitable(url
)
492 class BBVTVRecordingsIE(BBVTVBaseIE
):
493 _VALID_URL
= _create_valid_url(BBVTVBaseIE
._HOST
, r
'\d+', 'recording')
496 'url': 'https://bbv-tv.net/recordings?recording=193615508',
497 'only_matching': True,
499 'url': 'https://bbv-tv.net/tc/ptc_recordings_all_recordings?recording=193615420',
500 'only_matching': True,
504 class VTXTVBaseIE(ZattooPlatformBaseIE
):
505 _NETRC_MACHINE
= 'vtxtv'
507 _API_HOST
= 'www.%s' % _HOST
510 class VTXTVIE(VTXTVBaseIE
):
511 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
514 'url': 'https://vtxtv.ch/program/daserste/210177916',
515 'only_matching': True,
517 'url': 'https://vtxtv.ch/guide/german?channel=srf1&program=169860555',
518 'only_matching': True,
522 class VTXTVLiveIE(VTXTVBaseIE
):
523 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
526 'url': 'https://vtxtv.ch/channels/german?channel=srf_zwei',
527 'only_matching': True,
529 'url': 'https://vtxtv.ch/live/srf1',
530 'only_matching': True,
534 def suitable(cls
, url
):
535 return False if VTXTVIE
.suitable(url
) else super().suitable(url
)
538 class VTXTVRecordingsIE(VTXTVBaseIE
):
539 _VALID_URL
= _create_valid_url(VTXTVBaseIE
._HOST
, r
'\d+', 'recording')
542 'url': 'https://vtxtv.ch/recordings?recording=193615508',
543 'only_matching': True,
545 'url': 'https://vtxtv.ch/tc/ptc_recordings_all_recordings?recording=193615420',
546 'only_matching': True,
550 class GlattvisionTVBaseIE(ZattooPlatformBaseIE
):
551 _NETRC_MACHINE
= 'glattvisiontv'
552 _HOST
= 'iptv.glattvision.ch'
555 class GlattvisionTVIE(GlattvisionTVBaseIE
):
556 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
559 'url': 'https://iptv.glattvision.ch/program/daserste/210177916',
560 'only_matching': True,
562 'url': 'https://iptv.glattvision.ch/guide/german?channel=srf1&program=169860555',
563 'only_matching': True,
567 class GlattvisionTVLiveIE(GlattvisionTVBaseIE
):
568 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
571 'url': 'https://iptv.glattvision.ch/channels/german?channel=srf_zwei',
572 'only_matching': True,
574 'url': 'https://iptv.glattvision.ch/live/srf1',
575 'only_matching': True,
579 def suitable(cls
, url
):
580 return False if GlattvisionTVIE
.suitable(url
) else super().suitable(url
)
583 class GlattvisionTVRecordingsIE(GlattvisionTVBaseIE
):
584 _VALID_URL
= _create_valid_url(GlattvisionTVBaseIE
._HOST
, r
'\d+', 'recording')
587 'url': 'https://iptv.glattvision.ch/recordings?recording=193615508',
588 'only_matching': True,
590 'url': 'https://iptv.glattvision.ch/tc/ptc_recordings_all_recordings?recording=193615420',
591 'only_matching': True,
595 class SAKTVBaseIE(ZattooPlatformBaseIE
):
596 _NETRC_MACHINE
= 'saktv'
598 _API_HOST
= 'www.%s' % _HOST
601 class SAKTVIE(SAKTVBaseIE
):
602 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
605 'url': 'https://saktv.ch/program/daserste/210177916',
606 'only_matching': True,
608 'url': 'https://saktv.ch/guide/german?channel=srf1&program=169860555',
609 'only_matching': True,
613 class SAKTVLiveIE(SAKTVBaseIE
):
614 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
617 'url': 'https://saktv.ch/channels/german?channel=srf_zwei',
618 'only_matching': True,
620 'url': 'https://saktv.ch/live/srf1',
621 'only_matching': True,
625 def suitable(cls
, url
):
626 return False if SAKTVIE
.suitable(url
) else super().suitable(url
)
629 class SAKTVRecordingsIE(SAKTVBaseIE
):
630 _VALID_URL
= _create_valid_url(SAKTVBaseIE
._HOST
, r
'\d+', 'recording')
633 'url': 'https://saktv.ch/recordings?recording=193615508',
634 'only_matching': True,
636 'url': 'https://saktv.ch/tc/ptc_recordings_all_recordings?recording=193615420',
637 'only_matching': True,
641 class EWETVBaseIE(ZattooPlatformBaseIE
):
642 _NETRC_MACHINE
= 'ewetv'
643 _HOST
= 'tvonline.ewe.de'
646 class EWETVIE(EWETVBaseIE
):
647 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
650 'url': 'https://tvonline.ewe.de/program/daserste/210177916',
651 'only_matching': True,
653 'url': 'https://tvonline.ewe.de/guide/german?channel=srf1&program=169860555',
654 'only_matching': True,
658 class EWETVLiveIE(EWETVBaseIE
):
659 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
662 'url': 'https://tvonline.ewe.de/channels/german?channel=srf_zwei',
663 'only_matching': True,
665 'url': 'https://tvonline.ewe.de/live/srf1',
666 'only_matching': True,
670 def suitable(cls
, url
):
671 return False if EWETVIE
.suitable(url
) else super().suitable(url
)
674 class EWETVRecordingsIE(EWETVBaseIE
):
675 _VALID_URL
= _create_valid_url(EWETVBaseIE
._HOST
, r
'\d+', 'recording')
678 'url': 'https://tvonline.ewe.de/recordings?recording=193615508',
679 'only_matching': True,
681 'url': 'https://tvonline.ewe.de/tc/ptc_recordings_all_recordings?recording=193615420',
682 'only_matching': True,
686 class QuantumTVBaseIE(ZattooPlatformBaseIE
):
687 _NETRC_MACHINE
= 'quantumtv'
688 _HOST
= 'quantum-tv.com'
689 _API_HOST
= 'www.%s' % _HOST
692 class QuantumTVIE(QuantumTVBaseIE
):
693 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
696 'url': 'https://quantum-tv.com/program/daserste/210177916',
697 'only_matching': True,
699 'url': 'https://quantum-tv.com/guide/german?channel=srf1&program=169860555',
700 'only_matching': True,
704 class QuantumTVLiveIE(QuantumTVBaseIE
):
705 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
708 'url': 'https://quantum-tv.com/channels/german?channel=srf_zwei',
709 'only_matching': True,
711 'url': 'https://quantum-tv.com/live/srf1',
712 'only_matching': True,
716 def suitable(cls
, url
):
717 return False if QuantumTVIE
.suitable(url
) else super().suitable(url
)
720 class QuantumTVRecordingsIE(QuantumTVBaseIE
):
721 _VALID_URL
= _create_valid_url(QuantumTVBaseIE
._HOST
, r
'\d+', 'recording')
724 'url': 'https://quantum-tv.com/recordings?recording=193615508',
725 'only_matching': True,
727 'url': 'https://quantum-tv.com/tc/ptc_recordings_all_recordings?recording=193615420',
728 'only_matching': True,
732 class OsnatelTVBaseIE(ZattooPlatformBaseIE
):
733 _NETRC_MACHINE
= 'osnateltv'
734 _HOST
= 'tvonline.osnatel.de'
737 class OsnatelTVIE(OsnatelTVBaseIE
):
738 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
741 'url': 'https://tvonline.osnatel.de/program/daserste/210177916',
742 'only_matching': True,
744 'url': 'https://tvonline.osnatel.de/guide/german?channel=srf1&program=169860555',
745 'only_matching': True,
749 class OsnatelTVLiveIE(OsnatelTVBaseIE
):
750 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
753 'url': 'https://tvonline.osnatel.de/channels/german?channel=srf_zwei',
754 'only_matching': True,
756 'url': 'https://tvonline.osnatel.de/live/srf1',
757 'only_matching': True,
761 def suitable(cls
, url
):
762 return False if OsnatelTVIE
.suitable(url
) else super().suitable(url
)
765 class OsnatelTVRecordingsIE(OsnatelTVBaseIE
):
766 _VALID_URL
= _create_valid_url(OsnatelTVBaseIE
._HOST
, r
'\d+', 'recording')
769 'url': 'https://tvonline.osnatel.de/recordings?recording=193615508',
770 'only_matching': True,
772 'url': 'https://tvonline.osnatel.de/tc/ptc_recordings_all_recordings?recording=193615420',
773 'only_matching': True,
777 class EinsUndEinsTVBaseIE(ZattooPlatformBaseIE
):
778 _NETRC_MACHINE
= '1und1tv'
780 _API_HOST
= 'www.%s' % _HOST
783 class EinsUndEinsTVIE(EinsUndEinsTVBaseIE
):
784 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
787 'url': 'https://1und1.tv/program/daserste/210177916',
788 'only_matching': True,
790 'url': 'https://1und1.tv/guide/german?channel=srf1&program=169860555',
791 'only_matching': True,
795 class EinsUndEinsTVLiveIE(EinsUndEinsTVBaseIE
):
796 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
799 'url': 'https://1und1.tv/channels/german?channel=srf_zwei',
800 'only_matching': True,
802 'url': 'https://1und1.tv/live/srf1',
803 'only_matching': True,
807 def suitable(cls
, url
):
808 return False if EinsUndEinsTVIE
.suitable(url
) else super().suitable(url
)
811 class EinsUndEinsTVRecordingsIE(EinsUndEinsTVBaseIE
):
812 _VALID_URL
= _create_valid_url(EinsUndEinsTVBaseIE
._HOST
, r
'\d+', 'recording')
815 'url': 'https://1und1.tv/recordings?recording=193615508',
816 'only_matching': True,
818 'url': 'https://1und1.tv/tc/ptc_recordings_all_recordings?recording=193615420',
819 'only_matching': True,
823 class SaltTVBaseIE(ZattooPlatformBaseIE
):
824 _NETRC_MACHINE
= 'salttv'
828 class SaltTVIE(SaltTVBaseIE
):
829 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'\d+', 'program', '(?:program|watch)/[^/]+')
832 'url': 'https://tv.salt.ch/program/daserste/210177916',
833 'only_matching': True,
835 'url': 'https://tv.salt.ch/guide/german?channel=srf1&program=169860555',
836 'only_matching': True,
840 class SaltTVLiveIE(SaltTVBaseIE
):
841 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'[^/?&#]+', 'channel', 'live')
844 'url': 'https://tv.salt.ch/channels/german?channel=srf_zwei',
845 'only_matching': True,
847 'url': 'https://tv.salt.ch/live/srf1',
848 'only_matching': True,
852 def suitable(cls
, url
):
853 return False if SaltTVIE
.suitable(url
) else super().suitable(url
)
856 class SaltTVRecordingsIE(SaltTVBaseIE
):
857 _VALID_URL
= _create_valid_url(SaltTVBaseIE
._HOST
, r
'\d+', 'recording')
860 'url': 'https://tv.salt.ch/recordings?recording=193615508',
861 'only_matching': True,
863 'url': 'https://tv.salt.ch/tc/ptc_recordings_all_recordings?recording=193615420',
864 'only_matching': True,