3 from .common
import InfoExtractor
4 from ..networking
import HEADRequest
12 from ..utils
.traversal
import traverse_obj
15 class RedCDNLivxIE(InfoExtractor
):
16 _VALID_URL
= r
'https?://[^.]+\.(?:dcs\.redcdn|atmcdn)\.pl/(?:live(?:dash|hls|ss)|nvr)/o2/(?P<tenant>[^/?#]+)/(?P<id>[^?#]+)\.livx'
17 IE_NAME
= 'redcdnlivx'
20 'url': 'https://r.dcs.redcdn.pl/livedash/o2/senat/ENC02/channel.livx?indexMode=true&startTime=638272860000&stopTime=638292544000',
22 'id': 'ENC02-638272860000-638292544000',
25 'duration': 19683.982,
26 'live_status': 'was_live',
29 'url': 'https://r.dcs.redcdn.pl/livedash/o2/sejm/ENC18/live.livx?indexMode=true&startTime=722333096000&stopTime=722335562000',
31 'id': 'ENC18-722333096000-722335562000',
35 'live_status': 'was_live',
38 'url': 'https://r.dcs.redcdn.pl/livehls/o2/sportevolution/live/triathlon2018/warsaw.livx/playlist.m3u8?startTime=550305000000&stopTime=550327620000',
40 'id': 'triathlon2018-warsaw-550305000000-550327620000',
42 'title': 'triathlon2018/warsaw',
44 'live_status': 'was_live',
47 'url': 'https://n-25-12.dcs.redcdn.pl/nvr/o2/sejm/Migacz-ENC01/1.livx?startTime=722347200000&stopTime=722367345000',
48 'only_matching': True,
50 'url': 'https://redir.atmcdn.pl/nvr/o2/sejm/ENC08/1.livx?startTime=503831270000&stopTime=503840040000',
51 'only_matching': True,
55 Known methods (first in url path):
56 - `livedash` - DASH MPD
57 - `livehls` - HTTP Live Streaming
58 - `livess` - IIS Smooth Streaming
59 - `nvr` - CCTV mode, directly returns a file, typically flv, avc1, aac
60 - `sc` - shoutcast/icecast (audio streams, like radio)
63 def _real_extract(self
, url
):
64 tenant
, path
= self
._match
_valid
_url
(url
).group('tenant', 'id')
66 start_time
= traverse_obj(qs
, ('startTime', 0, {int_or_none}
))
67 stop_time
= traverse_obj(qs
, ('stopTime', 0, {int_or_none}
))
73 elif mode
== 'livehls':
74 suffix
= '/playlist.m3u8'
77 file_qs
['startTime'] = start_time
79 file_qs
['stopTime'] = stop_time
81 file_qs
['nolimit'] = 1
83 file_qs
['indexMode'] = 'true'
84 return update_url_query(f
'https://r.dcs.redcdn.pl/{mode}/o2/{tenant}/{path}.livx{suffix}', file_qs
)
86 # no id or title for a transmission. making ones up.
88 .replace('/live', '').replace('live/', '') \
89 .replace('/channel', '').replace('channel/', '') \
91 video_id
= join_nonempty(title
.replace('/', '-'), start_time
, stop_time
)
94 # downloading the manifest separately here instead of _extract_ism_formats to also get some stream metadata
95 ism_res
= self
._download
_xml
_handle
(
96 livx_mode('livess'), video_id
,
97 note
='Downloading ISM manifest',
98 errnote
='Failed to download ISM manifest',
101 if ism_res
is not False:
102 ism_doc
, ism_urlh
= ism_res
103 formats
, _
= self
._parse
_ism
_formats
_and
_subtitles
(ism_doc
, ism_urlh
.url
, 'ss')
105 nvr_urlh
= self
._request
_webpage
(
106 HEADRequest(livx_mode('nvr')), video_id
, 'Follow flv file redirect', fatal
=False,
107 expected_status
=lambda _
: True)
108 if nvr_urlh
and nvr_urlh
.status
== 200:
112 'format_id': 'direct-0',
113 'preference': -1, # might be slow
115 formats
.extend(self
._extract
_mpd
_formats
(livx_mode('livedash'), video_id
, mpd_id
='dash', fatal
=False))
116 formats
.extend(self
._extract
_m
3u8_formats
(
117 livx_mode('livehls'), video_id
, m3u8_id
='hls', ext
='mp4', fatal
=False))
119 time_scale
= traverse_obj(ism_doc
, ('@TimeScale', {int_or_none}
)) or 10000000
120 duration
= traverse_obj(
121 ism_doc
, ('@Duration', {functools.partial(float_or_none, scale=time_scale)}
)) or None
124 if traverse_obj(ism_doc
, '@IsLive') == 'TRUE':
125 live_status
= 'is_live'
127 live_status
= 'was_live'
133 'duration': duration
,
134 'live_status': live_status
,