1 from .common
import InfoExtractor
11 class HiDiveIE(InfoExtractor
):
12 _VALID_URL
= r
'https?://(?:www\.)?hidive\.com/stream/(?P<id>(?P<title>[^/]+)/(?P<key>[^/?#&]+))'
13 # Using X-Forwarded-For results in 403 HTTP error for HLS fragments,
14 # so disabling geo bypass completely
16 _NETRC_MACHINE
= 'hidive'
17 _LOGIN_URL
= 'https://www.hidive.com/account/login'
20 'url': 'https://www.hidive.com/stream/the-comic-artist-and-his-assistants/s01e001',
22 'id': 'the-comic-artist-and-his-assistants/s01e001',
24 'title': 'the-comic-artist-and-his-assistants/s01e001',
25 'series': 'the-comic-artist-and-his-assistants',
30 'skip_download': True,
32 'skip': 'Requires Authentication',
35 def _perform_login(self
, username
, password
):
36 webpage
= self
._download
_webpage
(self
._LOGIN
_URL
, None)
37 form
= self
._search
_regex
(
38 r
'(?s)<form[^>]+action="/account/login"[^>]*>(.+?)</form>',
39 webpage
, 'login form', default
=None)
42 data
= self
._hidden
_inputs
(form
)
47 login_webpage
= self
._download
_webpage
(
48 self
._LOGIN
_URL
, None, 'Logging in', data
=urlencode_postdata(data
))
49 # If the user has multiple profiles on their account, select one. For now pick the first profile.
50 profile_id
= self
._search
_regex
(
51 r
'<button [^>]+?data-profile-id="(\w+)"', login_webpage
, 'profile id', default
=None)
52 if profile_id
is None:
53 return # If only one profile, Hidive auto-selects it
54 self
._request
_webpage
(
55 'https://www.hidive.com/ajax/chooseprofile', None,
56 data
=urlencode_postdata({
57 'profileId': profile_id
,
58 'hash': self
._search
_regex
(
59 r
'\<button [^>]+?data-hash="(\w+)"', login_webpage
, 'profile id hash'),
60 'returnUrl': '/dashboard',
63 def _call_api(self
, video_id
, title
, key
, data
={}, **kwargs
):
68 'PlayerId': 'f4f895ce1ca713ba263b91caeb1daa2d08904783',
70 return self
._download
_json
(
71 'https://www.hidive.com/play/settings', video_id
,
72 data
=urlencode_postdata(data
), **kwargs
) or {}
74 def _real_extract(self
, url
):
75 video_id
, title
, key
= self
._match
_valid
_url
(url
).group('id', 'title', 'key')
76 settings
= self
._call
_api
(video_id
, title
, key
)
78 restriction
= settings
.get('restrictionReason')
79 if restriction
== 'RegionRestricted':
80 self
.raise_geo_restricted()
81 if restriction
and restriction
!= 'None':
83 f
'{self.IE_NAME} said: {restriction}', expected
=True)
85 formats
, parsed_urls
= [], {None}
86 for rendition_id
, rendition
in settings
['renditions'].items():
87 audio
, version
, extra
= rendition_id
.split('_')
88 m3u8_url
= url_or_none(try_get(rendition
, lambda x
: x
['bitrates']['hls']))
89 if m3u8_url
not in parsed_urls
:
90 parsed_urls
.add(m3u8_url
)
91 frmt
= self
._extract
_m
3u8_formats
(
92 m3u8_url
, video_id
, 'mp4', entry_protocol
='m3u8_native', m3u8_id
=rendition_id
, fatal
=False)
95 f
['format_note'] = f
'{version}, {extra}'
99 for rendition_id
, rendition
in settings
['renditions'].items():
100 audio
, version
, extra
= rendition_id
.split('_')
101 for cc_file
in rendition
.get('ccFiles') or []:
102 cc_url
= url_or_none(try_get(cc_file
, lambda x
: x
[2]))
103 cc_lang
= try_get(cc_file
, (lambda x
: x
[1].replace(' ', '-').lower(), lambda x
: x
[0]), str)
104 if cc_url
not in parsed_urls
and cc_lang
:
105 parsed_urls
.add(cc_url
)
106 subtitles
.setdefault(cc_lang
, []).append({'url': cc_url
})
111 'subtitles': subtitles
,
114 'season_number': int_or_none(
115 self
._search
_regex
(r
's(\d+)', key
, 'season number', default
=None)),
116 'episode_number': int_or_none(
117 self
._search
_regex
(r
'e(\d+)', key
, 'episode number', default
=None)),
118 'http_headers': {'Referer': url
},