duboku.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import re
  2. from .common import InfoExtractor
  3. from ..compat import compat_urlparse
  4. from ..utils import (
  5. clean_html,
  6. extract_attributes,
  7. ExtractorError,
  8. get_elements_by_class,
  9. int_or_none,
  10. js_to_json,
  11. smuggle_url,
  12. unescapeHTML,
  13. )
  14. def _get_elements_by_tag_and_attrib(html, tag=None, attribute=None, value=None, escape_value=True):
  15. """Return the content of the tag with the specified attribute in the passed HTML document"""
  16. if tag is None:
  17. tag = '[a-zA-Z0-9:._-]+'
  18. if attribute is None:
  19. attribute = ''
  20. else:
  21. attribute = r'\s+(?P<attribute>%s)' % re.escape(attribute)
  22. if value is None:
  23. value = ''
  24. else:
  25. value = re.escape(value) if escape_value else value
  26. value = '=[\'"]?(?P<value>%s)[\'"]?' % value
  27. retlist = []
  28. for m in re.finditer(r'''(?xs)
  29. <(?P<tag>%s)
  30. (?:\s+[a-zA-Z0-9:._-]+(?:=[a-zA-Z0-9:._-]*|="[^"]*"|='[^']*'|))*?
  31. %s%s
  32. (?:\s+[a-zA-Z0-9:._-]+(?:=[a-zA-Z0-9:._-]*|="[^"]*"|='[^']*'|))*?
  33. \s*>
  34. (?P<content>.*?)
  35. </\1>
  36. ''' % (tag, attribute, value), html):
  37. retlist.append(m)
  38. return retlist
  39. def _get_element_by_tag_and_attrib(html, tag=None, attribute=None, value=None, escape_value=True):
  40. retval = _get_elements_by_tag_and_attrib(html, tag, attribute, value, escape_value)
  41. return retval[0] if retval else None
  42. class DubokuIE(InfoExtractor):
  43. IE_NAME = 'duboku'
  44. IE_DESC = 'www.duboku.io'
  45. _VALID_URL = r'(?:https?://[^/]+\.duboku\.io/vodplay/)(?P<id>[0-9]+-[0-9-]+)\.html.*'
  46. _TESTS = [{
  47. 'url': 'https://w.duboku.io/vodplay/1575-1-1.html',
  48. 'info_dict': {
  49. 'id': '1575-1-1',
  50. 'ext': 'mp4',
  51. 'series': '白色月光',
  52. 'title': 'contains:白色月光',
  53. 'season_number': 1,
  54. 'episode_number': 1,
  55. 'season': 'Season 1',
  56. 'episode_id': '1',
  57. 'season_id': '1',
  58. 'episode': 'Episode 1',
  59. },
  60. 'params': {
  61. 'skip_download': 'm3u8 download',
  62. },
  63. }, {
  64. 'url': 'https://w.duboku.io/vodplay/1588-1-1.html',
  65. 'info_dict': {
  66. 'id': '1588-1-1',
  67. 'ext': 'mp4',
  68. 'series': '亲爱的自己',
  69. 'title': 'contains:第1集',
  70. 'season_number': 1,
  71. 'episode_number': 1,
  72. 'episode': 'Episode 1',
  73. 'season': 'Season 1',
  74. 'episode_id': '1',
  75. 'season_id': '1',
  76. },
  77. 'params': {
  78. 'skip_download': 'm3u8 download',
  79. },
  80. }]
  81. _PLAYER_DATA_PATTERN = r'player_data\s*=\s*(\{\s*(.*)})\s*;?\s*</script'
  82. def _real_extract(self, url):
  83. video_id = self._match_id(url)
  84. temp = video_id.split('-')
  85. series_id = temp[0]
  86. season_id = temp[1]
  87. episode_id = temp[2]
  88. webpage_url = 'https://w.duboku.io/vodplay/%s.html' % video_id
  89. webpage_html = self._download_webpage(webpage_url, video_id)
  90. # extract video url
  91. player_data = self._search_regex(
  92. self._PLAYER_DATA_PATTERN, webpage_html, 'player_data')
  93. player_data = self._parse_json(player_data, video_id, js_to_json)
  94. # extract title
  95. temp = get_elements_by_class('title', webpage_html)
  96. series_title = None
  97. title = None
  98. for html in temp:
  99. mobj = re.search(r'<a\s+.*>(.*)</a>', html)
  100. if mobj:
  101. href = extract_attributes(mobj.group(0)).get('href')
  102. if href:
  103. mobj1 = re.search(r'/(\d+)\.html', href)
  104. if mobj1 and mobj1.group(1) == series_id:
  105. series_title = clean_html(mobj.group(0))
  106. series_title = re.sub(r'[\s\r\n\t]+', ' ', series_title)
  107. title = clean_html(html)
  108. title = re.sub(r'[\s\r\n\t]+', ' ', title)
  109. break
  110. data_url = player_data.get('url')
  111. if not data_url:
  112. raise ExtractorError('Cannot find url in player_data')
  113. data_from = player_data.get('from')
  114. # if it is an embedded iframe, maybe it's an external source
  115. headers = {'Referer': webpage_url}
  116. if data_from == 'iframe':
  117. # use _type url_transparent to retain the meaningful details
  118. # of the video.
  119. return {
  120. '_type': 'url_transparent',
  121. 'url': smuggle_url(data_url, {'http_headers': headers}),
  122. 'id': video_id,
  123. 'title': title,
  124. 'series': series_title,
  125. 'season_number': int_or_none(season_id),
  126. 'season_id': season_id,
  127. 'episode_number': int_or_none(episode_id),
  128. 'episode_id': episode_id,
  129. }
  130. formats = self._extract_m3u8_formats(data_url, video_id, 'mp4', headers=headers)
  131. return {
  132. 'id': video_id,
  133. 'title': title,
  134. 'series': series_title,
  135. 'season_number': int_or_none(season_id),
  136. 'season_id': season_id,
  137. 'episode_number': int_or_none(episode_id),
  138. 'episode_id': episode_id,
  139. 'formats': formats,
  140. 'http_headers': headers
  141. }
  142. class DubokuPlaylistIE(InfoExtractor):
  143. IE_NAME = 'duboku:list'
  144. IE_DESC = 'www.duboku.io entire series'
  145. _VALID_URL = r'(?:https?://[^/]+\.duboku\.io/voddetail/)(?P<id>[0-9]+)\.html.*'
  146. _TESTS = [{
  147. 'url': 'https://w.duboku.io/voddetail/1575.html',
  148. 'info_dict': {
  149. 'id': 'startswith:1575',
  150. 'title': '白色月光',
  151. },
  152. 'playlist_count': 12,
  153. }, {
  154. 'url': 'https://w.duboku.io/voddetail/1554.html',
  155. 'info_dict': {
  156. 'id': 'startswith:1554',
  157. 'title': '以家人之名',
  158. },
  159. 'playlist_mincount': 30,
  160. }]
  161. def _real_extract(self, url):
  162. mobj = self._match_valid_url(url)
  163. if mobj is None:
  164. raise ExtractorError('Invalid URL: %s' % url)
  165. series_id = mobj.group('id')
  166. fragment = compat_urlparse.urlparse(url).fragment
  167. webpage_url = 'https://w.duboku.io/voddetail/%s.html' % series_id
  168. webpage_html = self._download_webpage(webpage_url, series_id)
  169. # extract title
  170. title = _get_element_by_tag_and_attrib(webpage_html, 'h1', 'class', 'title')
  171. title = unescapeHTML(title.group('content')) if title else None
  172. if not title:
  173. title = self._html_search_meta('keywords', webpage_html)
  174. if not title:
  175. title = _get_element_by_tag_and_attrib(webpage_html, 'title')
  176. title = unescapeHTML(title.group('content')) if title else None
  177. # extract playlists
  178. playlists = {}
  179. for div in _get_elements_by_tag_and_attrib(
  180. webpage_html, attribute='id', value='playlist\\d+', escape_value=False):
  181. playlist_id = div.group('value')
  182. playlist = []
  183. for a in _get_elements_by_tag_and_attrib(
  184. div.group('content'), 'a', 'href', value='[^\'"]+?', escape_value=False):
  185. playlist.append({
  186. 'href': unescapeHTML(a.group('value')),
  187. 'title': unescapeHTML(a.group('content'))
  188. })
  189. playlists[playlist_id] = playlist
  190. # select the specified playlist if url fragment exists
  191. playlist = None
  192. playlist_id = None
  193. if fragment:
  194. playlist = playlists.get(fragment)
  195. playlist_id = fragment
  196. else:
  197. first = next(iter(playlists.items()), None)
  198. if first:
  199. (playlist_id, playlist) = first
  200. if not playlist:
  201. raise ExtractorError(
  202. 'Cannot find %s' % fragment if fragment else 'Cannot extract playlist')
  203. # return url results
  204. return self.playlist_result([
  205. self.url_result(
  206. compat_urlparse.urljoin('https://w.duboku.io', x['href']),
  207. ie=DubokuIE.ie_key(), video_title=x.get('title'))
  208. for x in playlist], series_id + '#' + playlist_id, title)