videomore.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. from .common import InfoExtractor
  2. from ..utils import (
  3. int_or_none,
  4. parse_qs,
  5. )
  6. class VideomoreBaseIE(InfoExtractor):
  7. _API_BASE_URL = 'https://more.tv/api/v3/web/'
  8. _VALID_URL_BASE = r'https?://(?:videomore\.ru|more\.tv)/'
  9. def _download_page_data(self, display_id):
  10. return self._download_json(
  11. self._API_BASE_URL + 'PageData', display_id, query={
  12. 'url': '/' + display_id,
  13. })['attributes']['response']['data']
  14. def _track_url_result(self, track):
  15. track_vod = track['trackVod']
  16. video_url = track_vod.get('playerLink') or track_vod['link']
  17. return self.url_result(
  18. video_url, VideomoreIE.ie_key(), track_vod.get('hubId'))
  19. class VideomoreIE(InfoExtractor):
  20. IE_NAME = 'videomore'
  21. _VALID_URL = r'''(?x)
  22. videomore:(?P<sid>\d+)$|
  23. https?://
  24. (?:
  25. videomore\.ru/
  26. (?:
  27. embed|
  28. [^/]+/[^/]+
  29. )/|
  30. (?:
  31. (?:player\.)?videomore\.ru|
  32. siren\.more\.tv/player
  33. )/[^/]*\?.*?\btrack_id=|
  34. odysseus\.more.tv/player/(?P<partner_id>\d+)/
  35. )
  36. (?P<id>\d+)
  37. (?:[/?#&]|\.(?:xml|json)|$)
  38. '''
  39. _EMBED_REGEX = [r'''(?x)
  40. (?:
  41. <iframe[^>]+src=([\'"])|
  42. <object[^>]+data=(["\'])https?://videomore\.ru/player\.swf\?.*config=
  43. )(?P<url>https?://videomore\.ru/[^?#"']+/\d+(?:\.xml)?)
  44. ''']
  45. _TESTS = [{
  46. 'url': 'http://videomore.ru/kino_v_detalayah/5_sezon/367617',
  47. 'md5': '44455a346edc0d509ac5b5a5b531dc35',
  48. 'info_dict': {
  49. 'id': '367617',
  50. 'ext': 'flv',
  51. 'title': 'Кино в деталях 5 сезон В гостях Алексей Чумаков и Юлия Ковальчук',
  52. 'series': 'Кино в деталях',
  53. 'episode': 'В гостях Алексей Чумаков и Юлия Ковальчук',
  54. 'thumbnail': r're:^https?://.*\.jpg',
  55. 'duration': 2910,
  56. 'view_count': int,
  57. 'comment_count': int,
  58. 'age_limit': 16,
  59. },
  60. 'skip': 'The video is not available for viewing.',
  61. }, {
  62. 'url': 'http://videomore.ru/embed/259974',
  63. 'info_dict': {
  64. 'id': '259974',
  65. 'ext': 'mp4',
  66. 'title': 'Молодежка 2 сезон 40 серия',
  67. 'series': 'Молодежка',
  68. 'season': '2 сезон',
  69. 'episode': '40 серия',
  70. 'thumbnail': r're:^https?://.*\.jpg',
  71. 'duration': 2789,
  72. 'view_count': int,
  73. 'age_limit': 16,
  74. },
  75. 'params': {
  76. 'skip_download': True,
  77. },
  78. }, {
  79. 'url': 'http://videomore.ru/molodezhka/sezon_promo/341073',
  80. 'info_dict': {
  81. 'id': '341073',
  82. 'ext': 'flv',
  83. 'title': 'Промо Команда проиграла из-за Бакина?',
  84. 'episode': 'Команда проиграла из-за Бакина?',
  85. 'thumbnail': r're:^https?://.*\.jpg',
  86. 'duration': 29,
  87. 'age_limit': 16,
  88. 'view_count': int,
  89. },
  90. 'params': {
  91. 'skip_download': True,
  92. },
  93. 'skip': 'The video is not available for viewing.',
  94. }, {
  95. 'url': 'http://videomore.ru/elki_3?track_id=364623',
  96. 'only_matching': True,
  97. }, {
  98. 'url': 'http://videomore.ru/embed/364623',
  99. 'only_matching': True,
  100. }, {
  101. 'url': 'http://videomore.ru/video/tracks/364623.xml',
  102. 'only_matching': True,
  103. }, {
  104. 'url': 'http://videomore.ru/video/tracks/364623.json',
  105. 'only_matching': True,
  106. }, {
  107. 'url': 'http://videomore.ru/video/tracks/158031/quotes/33248',
  108. 'only_matching': True,
  109. }, {
  110. 'url': 'videomore:367617',
  111. 'only_matching': True,
  112. }, {
  113. 'url': 'https://player.videomore.ru/?partner_id=97&track_id=736234&autoplay=0&userToken=',
  114. 'only_matching': True,
  115. }, {
  116. 'url': 'https://odysseus.more.tv/player/1788/352317',
  117. 'only_matching': True,
  118. }, {
  119. 'url': 'https://siren.more.tv/player/config?track_id=352317&partner_id=1788&user_token=',
  120. 'only_matching': True,
  121. }]
  122. _GEO_BYPASS = False
  123. def _real_extract(self, url):
  124. mobj = self._match_valid_url(url)
  125. video_id = mobj.group('sid') or mobj.group('id')
  126. partner_id = mobj.group('partner_id') or parse_qs(url).get('partner_id', [None])[0] or '97'
  127. item = self._download_json(
  128. 'https://siren.more.tv/player/config', video_id, query={
  129. 'partner_id': partner_id,
  130. 'track_id': video_id,
  131. })['data']['playlist']['items'][0]
  132. title = item.get('title')
  133. series = item.get('project_name')
  134. season = item.get('season_name')
  135. episode = item.get('episode_name')
  136. if not title:
  137. title = []
  138. for v in (series, season, episode):
  139. if v:
  140. title.append(v)
  141. title = ' '.join(title)
  142. streams = item.get('streams') or []
  143. for protocol in ('DASH', 'HLS'):
  144. stream_url = item.get(protocol.lower() + '_url')
  145. if stream_url:
  146. streams.append({'protocol': protocol, 'url': stream_url})
  147. formats = []
  148. for stream in streams:
  149. stream_url = stream.get('url')
  150. if not stream_url:
  151. continue
  152. protocol = stream.get('protocol')
  153. if protocol == 'DASH':
  154. formats.extend(self._extract_mpd_formats(
  155. stream_url, video_id, mpd_id='dash', fatal=False))
  156. elif protocol == 'HLS':
  157. formats.extend(self._extract_m3u8_formats(
  158. stream_url, video_id, 'mp4', 'm3u8_native',
  159. m3u8_id='hls', fatal=False))
  160. elif protocol == 'MSS':
  161. formats.extend(self._extract_ism_formats(
  162. stream_url, video_id, ism_id='mss', fatal=False))
  163. if not formats:
  164. error = item.get('error')
  165. if error:
  166. if error in ('Данное видео недоступно для просмотра на территории этой страны', 'Данное видео доступно для просмотра только на территории России'):
  167. self.raise_geo_restricted(countries=['RU'], metadata_available=True)
  168. self.raise_no_formats(error, expected=True)
  169. return {
  170. 'id': video_id,
  171. 'title': title,
  172. 'series': series,
  173. 'season': season,
  174. 'episode': episode,
  175. 'thumbnail': item.get('thumbnail_url'),
  176. 'duration': int_or_none(item.get('duration')),
  177. 'view_count': int_or_none(item.get('views')),
  178. 'age_limit': int_or_none(item.get('min_age')),
  179. 'formats': formats,
  180. }
  181. class VideomoreVideoIE(VideomoreBaseIE):
  182. IE_NAME = 'videomore:video'
  183. _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?P<id>(?:(?:[^/]+/){2})?[^/?#&]+)(?:/*|[?#&].*?)$'
  184. _TESTS = [{
  185. # single video with og:video:iframe
  186. 'url': 'http://videomore.ru/elki_3',
  187. 'info_dict': {
  188. 'id': '364623',
  189. 'ext': 'flv',
  190. 'title': 'Ёлки 3',
  191. 'description': '',
  192. 'thumbnail': r're:^https?://.*\.jpg',
  193. 'duration': 5579,
  194. 'age_limit': 6,
  195. 'view_count': int,
  196. },
  197. 'params': {
  198. 'skip_download': True,
  199. },
  200. 'skip': 'Requires logging in',
  201. }, {
  202. # season single series with og:video:iframe
  203. 'url': 'http://videomore.ru/poslednii_ment/1_sezon/14_seriya',
  204. 'info_dict': {
  205. 'id': '352317',
  206. 'ext': 'mp4',
  207. 'title': 'Последний мент 1 сезон 14 серия',
  208. 'series': 'Последний мент',
  209. 'season': '1 сезон',
  210. 'episode': '14 серия',
  211. 'thumbnail': r're:^https?://.*\.jpg',
  212. 'duration': 2464,
  213. 'age_limit': 16,
  214. 'view_count': int,
  215. },
  216. 'params': {
  217. 'skip_download': True,
  218. },
  219. }, {
  220. 'url': 'http://videomore.ru/sejchas_v_seti/serii_221-240/226_vypusk',
  221. 'only_matching': True,
  222. }, {
  223. # single video without og:video:iframe
  224. 'url': 'http://videomore.ru/marin_i_ego_druzya',
  225. 'info_dict': {
  226. 'id': '359073',
  227. 'ext': 'flv',
  228. 'title': '1 серия. Здравствуй, Аквавилль!',
  229. 'description': 'md5:c6003179538b5d353e7bcd5b1372b2d7',
  230. 'thumbnail': r're:^https?://.*\.jpg',
  231. 'duration': 754,
  232. 'age_limit': 6,
  233. 'view_count': int,
  234. },
  235. 'params': {
  236. 'skip_download': True,
  237. },
  238. 'skip': 'redirects to https://more.tv/',
  239. }, {
  240. 'url': 'https://videomore.ru/molodezhka/6_sezon/29_seriya?utm_so',
  241. 'only_matching': True,
  242. }, {
  243. 'url': 'https://more.tv/poslednii_ment/1_sezon/14_seriya',
  244. 'only_matching': True,
  245. }]
  246. @classmethod
  247. def suitable(cls, url):
  248. return False if VideomoreIE.suitable(url) else super().suitable(url)
  249. def _real_extract(self, url):
  250. display_id = self._match_id(url)
  251. return self._track_url_result(self._download_page_data(display_id))
  252. class VideomoreSeasonIE(VideomoreBaseIE):
  253. IE_NAME = 'videomore:season'
  254. _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?!embed)(?P<id>[^/]+/[^/?#&]+)(?:/*|[?#&].*?)$'
  255. _TESTS = [{
  256. 'url': 'http://videomore.ru/molodezhka/film_o_filme',
  257. 'info_dict': {
  258. 'id': 'molodezhka/film_o_filme',
  259. 'title': 'Фильм о фильме',
  260. },
  261. 'playlist_mincount': 3,
  262. }, {
  263. 'url': 'http://videomore.ru/molodezhka/sezon_promo?utm_so',
  264. 'only_matching': True,
  265. }, {
  266. 'url': 'https://more.tv/molodezhka/film_o_filme',
  267. 'only_matching': True,
  268. }]
  269. @classmethod
  270. def suitable(cls, url):
  271. return (False if (VideomoreIE.suitable(url) or VideomoreVideoIE.suitable(url))
  272. else super().suitable(url))
  273. def _real_extract(self, url):
  274. display_id = self._match_id(url)
  275. season = self._download_page_data(display_id)
  276. season_id = str(season['id'])
  277. tracks = self._download_json(
  278. self._API_BASE_URL + f'seasons/{season_id}/tracks',
  279. season_id)['data']
  280. entries = []
  281. for track in tracks:
  282. entries.append(self._track_url_result(track))
  283. return self.playlist_result(entries, display_id, season.get('title'))