logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

chaturbate.py (5793B)


  1. import re
  2. from .common import InfoExtractor
  3. from ..utils import (
  4. ExtractorError,
  5. lowercase_escape,
  6. url_or_none,
  7. urlencode_postdata,
  8. )
  9. class ChaturbateIE(InfoExtractor):
  10. _VALID_URL = r'https?://(?:[^/]+\.)?chaturbate\.(?P<tld>com|eu|global)/(?:fullvideo/?\?.*?\bb=)?(?P<id>[^/?&#]+)'
  11. _TESTS = [{
  12. 'url': 'https://www.chaturbate.com/siswet19/',
  13. 'info_dict': {
  14. 'id': 'siswet19',
  15. 'ext': 'mp4',
  16. 'title': 're:^siswet19 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
  17. 'age_limit': 18,
  18. 'is_live': True,
  19. },
  20. 'params': {
  21. 'skip_download': True,
  22. },
  23. 'skip': 'Room is offline',
  24. }, {
  25. 'url': 'https://chaturbate.com/fullvideo/?b=caylin',
  26. 'only_matching': True,
  27. }, {
  28. 'url': 'https://en.chaturbate.com/siswet19/',
  29. 'only_matching': True,
  30. }, {
  31. 'url': 'https://chaturbate.eu/siswet19/',
  32. 'only_matching': True,
  33. }, {
  34. 'url': 'https://chaturbate.eu/fullvideo/?b=caylin',
  35. 'only_matching': True,
  36. }, {
  37. 'url': 'https://chaturbate.global/siswet19/',
  38. 'only_matching': True,
  39. }]
  40. _ERROR_MAP = {
  41. 'offline': 'Room is currently offline',
  42. 'private': 'Room is currently in a private show',
  43. 'away': 'Performer is currently away',
  44. 'password protected': 'Room is password protected',
  45. 'hidden': 'Hidden session in progress',
  46. }
  47. def _extract_from_api(self, video_id, tld):
  48. response = self._download_json(
  49. f'https://chaturbate.{tld}/get_edge_hls_url_ajax/', video_id,
  50. data=urlencode_postdata({'room_slug': video_id}),
  51. headers={
  52. **self.geo_verification_headers(),
  53. 'X-Requested-With': 'XMLHttpRequest',
  54. 'Accept': 'application/json',
  55. }, fatal=False, impersonate=True) or {}
  56. m3u8_url = response.get('url')
  57. if not m3u8_url:
  58. status = response.get('room_status')
  59. if error := self._ERROR_MAP.get(status):
  60. raise ExtractorError(error, expected=True)
  61. if status == 'public':
  62. self.raise_geo_restricted()
  63. self.report_warning(f'Got status "{status}" from API; falling back to webpage extraction')
  64. return None
  65. return {
  66. 'id': video_id,
  67. 'title': video_id,
  68. 'thumbnail': f'https://roomimg.stream.highwebmedia.com/ri/{video_id}.jpg',
  69. 'is_live': True,
  70. 'age_limit': 18,
  71. 'formats': self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=True),
  72. }
  73. def _extract_from_html(self, video_id, tld):
  74. webpage = self._download_webpage(
  75. f'https://chaturbate.{tld}/{video_id}/', video_id,
  76. headers=self.geo_verification_headers(), impersonate=True)
  77. found_m3u8_urls = []
  78. data = self._parse_json(
  79. self._search_regex(
  80. r'initialRoomDossier\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
  81. webpage, 'data', default='{}', group='value'),
  82. video_id, transform_source=lowercase_escape, fatal=False)
  83. if data:
  84. m3u8_url = url_or_none(data.get('hls_source'))
  85. if m3u8_url:
  86. found_m3u8_urls.append(m3u8_url)
  87. if not found_m3u8_urls:
  88. for m in re.finditer(
  89. r'(\\u002[27])(?P<url>http.+?\.m3u8.*?)\1', webpage):
  90. found_m3u8_urls.append(lowercase_escape(m.group('url')))
  91. if not found_m3u8_urls:
  92. for m in re.finditer(
  93. r'(["\'])(?P<url>http.+?\.m3u8.*?)\1', webpage):
  94. found_m3u8_urls.append(m.group('url'))
  95. m3u8_urls = []
  96. for found_m3u8_url in found_m3u8_urls:
  97. m3u8_fast_url, m3u8_no_fast_url = found_m3u8_url, found_m3u8_url.replace('_fast', '')
  98. for m3u8_url in (m3u8_fast_url, m3u8_no_fast_url):
  99. if m3u8_url not in m3u8_urls:
  100. m3u8_urls.append(m3u8_url)
  101. if not m3u8_urls:
  102. error = self._search_regex(
  103. [r'<span[^>]+class=(["\'])desc_span\1[^>]*>(?P<error>[^<]+)</span>',
  104. r'<div[^>]+id=(["\'])defchat\1[^>]*>\s*<p><strong>(?P<error>[^<]+)<'],
  105. webpage, 'error', group='error', default=None)
  106. if not error:
  107. if any(p in webpage for p in (
  108. self._ERROR_MAP['offline'], 'offline_tipping', 'tip_offline')):
  109. error = self._ERROR_MAP['offline']
  110. if error:
  111. raise ExtractorError(error, expected=True)
  112. raise ExtractorError('Unable to find stream URL')
  113. formats = []
  114. for m3u8_url in m3u8_urls:
  115. for known_id in ('fast', 'slow'):
  116. if f'_{known_id}' in m3u8_url:
  117. m3u8_id = known_id
  118. break
  119. else:
  120. m3u8_id = None
  121. formats.extend(self._extract_m3u8_formats(
  122. m3u8_url, video_id, ext='mp4',
  123. # ffmpeg skips segments for fast m3u8
  124. preference=-10 if m3u8_id == 'fast' else None,
  125. m3u8_id=m3u8_id, fatal=False, live=True))
  126. return {
  127. 'id': video_id,
  128. 'title': video_id,
  129. 'thumbnail': f'https://roomimg.stream.highwebmedia.com/ri/{video_id}.jpg',
  130. 'age_limit': self._rta_search(webpage),
  131. 'is_live': True,
  132. 'formats': formats,
  133. }
  134. def _real_extract(self, url):
  135. video_id, tld = self._match_valid_url(url).group('id', 'tld')
  136. return self._extract_from_api(video_id, tld) or self._extract_from_html(video_id, tld)