hidive.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from .common import InfoExtractor
  2. from ..utils import (
  3. ExtractorError,
  4. int_or_none,
  5. try_get,
  6. url_or_none,
  7. urlencode_postdata,
  8. )
  9. class HiDiveIE(InfoExtractor):
  10. _VALID_URL = r'https?://(?:www\.)?hidive\.com/stream/(?P<id>(?P<title>[^/]+)/(?P<key>[^/?#&]+))'
  11. # Using X-Forwarded-For results in 403 HTTP error for HLS fragments,
  12. # so disabling geo bypass completely
  13. _GEO_BYPASS = False
  14. _NETRC_MACHINE = 'hidive'
  15. _LOGIN_URL = 'https://www.hidive.com/account/login'
  16. _TESTS = [{
  17. 'url': 'https://www.hidive.com/stream/the-comic-artist-and-his-assistants/s01e001',
  18. 'info_dict': {
  19. 'id': 'the-comic-artist-and-his-assistants/s01e001',
  20. 'ext': 'mp4',
  21. 'title': 'the-comic-artist-and-his-assistants/s01e001',
  22. 'series': 'the-comic-artist-and-his-assistants',
  23. 'season_number': 1,
  24. 'episode_number': 1,
  25. },
  26. 'params': {
  27. 'skip_download': True,
  28. },
  29. 'skip': 'Requires Authentication',
  30. }]
  31. def _perform_login(self, username, password):
  32. webpage = self._download_webpage(self._LOGIN_URL, None)
  33. form = self._search_regex(
  34. r'(?s)<form[^>]+action="/account/login"[^>]*>(.+?)</form>',
  35. webpage, 'login form', default=None)
  36. if not form:
  37. return
  38. data = self._hidden_inputs(form)
  39. data.update({
  40. 'Email': username,
  41. 'Password': password,
  42. })
  43. login_webpage = self._download_webpage(
  44. self._LOGIN_URL, None, 'Logging in', data=urlencode_postdata(data))
  45. # If the user has multiple profiles on their account, select one. For now pick the first profile.
  46. profile_id = self._search_regex(
  47. r'<button [^>]+?data-profile-id="(\w+)"', login_webpage, 'profile id', default=None)
  48. if profile_id is None:
  49. return # If only one profile, Hidive auto-selects it
  50. self._request_webpage(
  51. 'https://www.hidive.com/ajax/chooseprofile', None,
  52. data=urlencode_postdata({
  53. 'profileId': profile_id,
  54. 'hash': self._search_regex(
  55. r'\<button [^>]+?data-hash="(\w+)"', login_webpage, 'profile id hash'),
  56. 'returnUrl': '/dashboard',
  57. }))
  58. def _call_api(self, video_id, title, key, data={}, **kwargs):
  59. data = {
  60. **data,
  61. 'Title': title,
  62. 'Key': key,
  63. 'PlayerId': 'f4f895ce1ca713ba263b91caeb1daa2d08904783',
  64. }
  65. return self._download_json(
  66. 'https://www.hidive.com/play/settings', video_id,
  67. data=urlencode_postdata(data), **kwargs) or {}
  68. def _real_extract(self, url):
  69. video_id, title, key = self._match_valid_url(url).group('id', 'title', 'key')
  70. settings = self._call_api(video_id, title, key)
  71. restriction = settings.get('restrictionReason')
  72. if restriction == 'RegionRestricted':
  73. self.raise_geo_restricted()
  74. if restriction and restriction != 'None':
  75. raise ExtractorError(
  76. f'{self.IE_NAME} said: {restriction}', expected=True)
  77. formats, parsed_urls = [], {None}
  78. for rendition_id, rendition in settings['renditions'].items():
  79. audio, version, extra = rendition_id.split('_')
  80. m3u8_url = url_or_none(try_get(rendition, lambda x: x['bitrates']['hls']))
  81. if m3u8_url not in parsed_urls:
  82. parsed_urls.add(m3u8_url)
  83. frmt = self._extract_m3u8_formats(
  84. m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id=rendition_id, fatal=False)
  85. for f in frmt:
  86. f['language'] = audio
  87. f['format_note'] = f'{version}, {extra}'
  88. formats.extend(frmt)
  89. subtitles = {}
  90. for rendition_id, rendition in settings['renditions'].items():
  91. audio, version, extra = rendition_id.split('_')
  92. for cc_file in rendition.get('ccFiles') or []:
  93. cc_url = url_or_none(try_get(cc_file, lambda x: x[2]))
  94. cc_lang = try_get(cc_file, (lambda x: x[1].replace(' ', '-').lower(), lambda x: x[0]), str)
  95. if cc_url not in parsed_urls and cc_lang:
  96. parsed_urls.add(cc_url)
  97. subtitles.setdefault(cc_lang, []).append({'url': cc_url})
  98. return {
  99. 'id': video_id,
  100. 'title': video_id,
  101. 'subtitles': subtitles,
  102. 'formats': formats,
  103. 'series': title,
  104. 'season_number': int_or_none(
  105. self._search_regex(r's(\d+)', key, 'season number', default=None)),
  106. 'episode_number': int_or_none(
  107. self._search_regex(r'e(\d+)', key, 'episode number', default=None)),
  108. 'http_headers': {'Referer': url},
  109. }