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

ertgr.py (12450B)


  1. import json
  2. import re
  3. from .common import InfoExtractor
  4. from ..utils import (
  5. ExtractorError,
  6. clean_html,
  7. determine_ext,
  8. dict_get,
  9. int_or_none,
  10. merge_dicts,
  11. parse_age_limit,
  12. parse_iso8601,
  13. parse_qs,
  14. str_or_none,
  15. try_get,
  16. url_or_none,
  17. variadic,
  18. )
  19. from ..utils.traversal import traverse_obj
  20. class ERTFlixBaseIE(InfoExtractor):
  21. def _call_api(
  22. self, video_id, method='Player/AcquireContent', api_version=1,
  23. param_headers=None, data=None, headers=None, **params):
  24. platform_codename = {'platformCodename': 'www'}
  25. headers_as_param = {'X-Api-Date-Format': 'iso', 'X-Api-Camel-Case': False}
  26. headers_as_param.update(param_headers or {})
  27. headers = headers or {}
  28. if data:
  29. headers['Content-Type'] = headers_as_param['Content-Type'] = 'application/json;charset=utf-8'
  30. data = json.dumps(merge_dicts(platform_codename, data)).encode()
  31. query = merge_dicts(
  32. {} if data else platform_codename,
  33. {'$headers': json.dumps(headers_as_param)},
  34. params)
  35. response = self._download_json(
  36. f'https://api.app.ertflix.gr/v{api_version!s}/{method}',
  37. video_id, fatal=False, query=query, data=data, headers=headers)
  38. if try_get(response, lambda x: x['Result']['Success']) is True:
  39. return response
  40. def _call_api_get_tiles(self, video_id, *tile_ids):
  41. requested_tile_ids = [video_id, *tile_ids]
  42. requested_tiles = [{'Id': tile_id} for tile_id in requested_tile_ids]
  43. tiles_response = self._call_api(
  44. video_id, method='Tile/GetTiles', api_version=2,
  45. data={'RequestedTiles': requested_tiles})
  46. tiles = try_get(tiles_response, lambda x: x['Tiles'], list) or []
  47. if tile_ids:
  48. if sorted([tile['Id'] for tile in tiles]) != sorted(requested_tile_ids):
  49. raise ExtractorError('Requested tiles not found', video_id=video_id)
  50. return tiles
  51. try:
  52. return next(tile for tile in tiles if tile['Id'] == video_id)
  53. except StopIteration:
  54. raise ExtractorError('No matching tile found', video_id=video_id)
  55. class ERTFlixCodenameIE(ERTFlixBaseIE):
  56. IE_NAME = 'ertflix:codename'
  57. IE_DESC = 'ERTFLIX videos by codename'
  58. _VALID_URL = r'ertflix:(?P<id>[\w-]+)'
  59. _TESTS = [{
  60. 'url': 'ertflix:monogramma-praxitelis-tzanoylinos',
  61. 'md5': '5b9c2cd171f09126167e4082fc1dd0ef',
  62. 'info_dict': {
  63. 'id': 'monogramma-praxitelis-tzanoylinos',
  64. 'ext': 'mp4',
  65. 'title': 'md5:ef0b439902963d56c43ac83c3f41dd0e',
  66. },
  67. },
  68. ]
  69. def _extract_formats_and_subs(self, video_id):
  70. media_info = self._call_api(video_id, codename=video_id)
  71. formats, subtitles = [], {}
  72. for media in traverse_obj(media_info, (
  73. 'MediaFiles', lambda _, v: v['RoleCodename'] == 'main',
  74. 'Formats', lambda _, v: url_or_none(v['Url']))):
  75. fmt_url = media['Url']
  76. ext = determine_ext(fmt_url)
  77. if ext == 'm3u8':
  78. fmts, subs = self._extract_m3u8_formats_and_subtitles(
  79. fmt_url, video_id, m3u8_id='hls', ext='mp4', fatal=False)
  80. elif ext == 'mpd':
  81. fmts, subs = self._extract_mpd_formats_and_subtitles(
  82. fmt_url, video_id, mpd_id='dash', fatal=False)
  83. else:
  84. formats.append({
  85. 'url': fmt_url,
  86. 'format_id': str_or_none(media.get('Id')),
  87. })
  88. continue
  89. formats.extend(fmts)
  90. self._merge_subtitles(subs, target=subtitles)
  91. return formats, subtitles
  92. def _real_extract(self, url):
  93. video_id = self._match_id(url)
  94. formats, subs = self._extract_formats_and_subs(video_id)
  95. if formats:
  96. return {
  97. 'id': video_id,
  98. 'formats': formats,
  99. 'subtitles': subs,
  100. 'title': self._generic_title(url),
  101. }
  102. class ERTFlixIE(ERTFlixBaseIE):
  103. IE_NAME = 'ertflix'
  104. IE_DESC = 'ERTFLIX videos'
  105. _VALID_URL = r'https?://www\.ertflix\.gr/(?:[^/]+/)?(?:series|vod)/(?P<id>[a-z]{3}\.\d+)'
  106. _TESTS = [{
  107. 'url': 'https://www.ertflix.gr/vod/vod.173258-aoratoi-ergates',
  108. 'md5': '6479d5e60fd7e520b07ba5411dcdd6e7',
  109. 'info_dict': {
  110. 'id': 'aoratoi-ergates',
  111. 'ext': 'mp4',
  112. 'title': 'md5:c1433d598fbba0211b0069021517f8b4',
  113. 'description': 'md5:01a64d113c31957eb7eb07719ab18ff4',
  114. 'thumbnail': r're:https?://.+\.jpg',
  115. 'episode_id': 'vod.173258',
  116. 'timestamp': 1639648800,
  117. 'upload_date': '20211216',
  118. 'duration': 3166,
  119. 'age_limit': 8,
  120. },
  121. }, {
  122. 'url': 'https://www.ertflix.gr/series/ser.3448-monogramma',
  123. 'info_dict': {
  124. 'id': 'ser.3448',
  125. 'age_limit': 8,
  126. 'description': 'Η εκπομπή σαράντα ετών που σημάδεψε τον πολιτισμό μας.',
  127. 'title': 'Μονόγραμμα',
  128. },
  129. 'playlist_mincount': 64,
  130. }, {
  131. 'url': 'https://www.ertflix.gr/series/ser.3448-monogramma?season=1',
  132. 'info_dict': {
  133. 'id': 'ser.3448',
  134. 'age_limit': 8,
  135. 'description': 'Η εκπομπή σαράντα ετών που σημάδεψε τον πολιτισμό μας.',
  136. 'title': 'Μονόγραμμα',
  137. },
  138. 'playlist_count': 22,
  139. }, {
  140. 'url': 'https://www.ertflix.gr/series/ser.3448-monogramma?season=1&season=2021%20-%202022',
  141. 'info_dict': {
  142. 'id': 'ser.3448',
  143. 'age_limit': 8,
  144. 'description': 'Η εκπομπή σαράντα ετών που σημάδεψε τον πολιτισμό μας.',
  145. 'title': 'Μονόγραμμα',
  146. },
  147. 'playlist_mincount': 36,
  148. }, {
  149. 'url': 'https://www.ertflix.gr/series/ser.164991-to-diktuo-1?season=1-9',
  150. 'info_dict': {
  151. 'id': 'ser.164991',
  152. 'age_limit': 8,
  153. 'description': 'Η πρώτη ελληνική εκπομπή με θεματολογία αποκλειστικά γύρω από το ίντερνετ.',
  154. 'title': 'Το δίκτυο',
  155. },
  156. 'playlist_mincount': 9,
  157. }, {
  158. 'url': 'https://www.ertflix.gr/en/vod/vod.127652-ta-kalytera-mas-chronia-ep1-mia-volta-sto-feggari',
  159. 'only_matching': True,
  160. }]
  161. def _extract_episode(self, episode):
  162. codename = try_get(episode, lambda x: x['Codename'], str)
  163. title = episode.get('Title')
  164. description = clean_html(dict_get(episode, ('ShortDescription', 'TinyDescription')))
  165. if not codename or not title or not episode.get('HasPlayableStream', True):
  166. return
  167. thumbnail = next((
  168. url_or_none(thumb.get('Url'))
  169. for thumb in variadic(dict_get(episode, ('Images', 'Image')) or {})
  170. if thumb.get('IsMain')),
  171. None)
  172. return {
  173. '_type': 'url_transparent',
  174. 'thumbnail': thumbnail,
  175. 'id': codename,
  176. 'episode_id': episode.get('Id'),
  177. 'title': title,
  178. 'alt_title': episode.get('Subtitle'),
  179. 'description': description,
  180. 'timestamp': parse_iso8601(episode.get('PublishDate')),
  181. 'duration': episode.get('DurationSeconds'),
  182. 'age_limit': self._parse_age_rating(episode),
  183. 'url': f'ertflix:{codename}',
  184. }
  185. @staticmethod
  186. def _parse_age_rating(info_dict):
  187. return parse_age_limit(
  188. info_dict.get('AgeRating')
  189. or (info_dict.get('IsAdultContent') and 18)
  190. or (info_dict.get('IsKidsContent') and 0))
  191. def _extract_series(self, video_id, season_titles=None, season_numbers=None):
  192. media_info = self._call_api(video_id, method='Tile/GetSeriesDetails', id=video_id)
  193. series = try_get(media_info, lambda x: x['Series'], dict) or {}
  194. series_info = {
  195. 'age_limit': self._parse_age_rating(series),
  196. 'title': series.get('Title'),
  197. 'description': dict_get(series, ('ShortDescription', 'TinyDescription')),
  198. }
  199. if season_numbers:
  200. season_titles = season_titles or []
  201. for season in try_get(series, lambda x: x['Seasons'], list) or []:
  202. if season.get('SeasonNumber') in season_numbers and season.get('Title'):
  203. season_titles.append(season['Title'])
  204. def gen_episode(m_info, season_titles):
  205. for episode_group in try_get(m_info, lambda x: x['EpisodeGroups'], list) or []:
  206. if season_titles and episode_group.get('Title') not in season_titles:
  207. continue
  208. episodes = try_get(episode_group, lambda x: x['Episodes'], list)
  209. if not episodes:
  210. continue
  211. season_info = {
  212. 'season': episode_group.get('Title'),
  213. 'season_number': int_or_none(episode_group.get('SeasonNumber')),
  214. }
  215. try:
  216. episodes = [(int(ep['EpisodeNumber']), ep) for ep in episodes]
  217. episodes.sort()
  218. except (KeyError, ValueError):
  219. episodes = enumerate(episodes, 1)
  220. for n, episode in episodes:
  221. info = self._extract_episode(episode)
  222. if info is None:
  223. continue
  224. info['episode_number'] = n
  225. info.update(season_info)
  226. yield info
  227. return self.playlist_result(
  228. gen_episode(media_info, season_titles), playlist_id=video_id, **series_info)
  229. def _real_extract(self, url):
  230. video_id = self._match_id(url)
  231. if video_id.startswith('ser.'):
  232. param_season = parse_qs(url).get('season', [None])
  233. param_season = [
  234. (have_number, int_or_none(v) if have_number else str_or_none(v))
  235. for have_number, v in
  236. [(int_or_none(ps) is not None, ps) for ps in param_season]
  237. if v is not None
  238. ]
  239. season_kwargs = {
  240. k: [v for is_num, v in param_season if is_num is c] or None
  241. for k, c in
  242. [('season_titles', False), ('season_numbers', True)]
  243. }
  244. return self._extract_series(video_id, **season_kwargs)
  245. return self._extract_episode(self._call_api_get_tiles(video_id))
  246. class ERTWebtvEmbedIE(InfoExtractor):
  247. IE_NAME = 'ertwebtv:embed'
  248. IE_DESC = 'ert.gr webtv embedded videos'
  249. _BASE_PLAYER_URL_RE = re.escape('//www.ert.gr/webtv/live-uni/vod/dt-uni-vod.php')
  250. _VALID_URL = rf'https?:{_BASE_PLAYER_URL_RE}\?([^#]+&)?f=(?P<id>[^#&]+)'
  251. _EMBED_REGEX = [rf'<iframe[^>]+?src=(?P<_q1>["\'])(?P<url>(?:https?:)?{_BASE_PLAYER_URL_RE}\?(?:(?!(?P=_q1)).)+)(?P=_q1)']
  252. _TESTS = [{
  253. 'url': 'https://www.ert.gr/webtv/live-uni/vod/dt-uni-vod.php?f=trailers/E2251_TO_DIKTYO_E09_16-01_1900.mp4&bgimg=/photos/2022/1/to_diktio_ep09_i_istoria_tou_diadiktiou_stin_Ellada_1021x576.jpg',
  254. 'md5': 'f9e9900c25c26f4ecfbddbb4b6305854',
  255. 'info_dict': {
  256. 'id': 'trailers/E2251_TO_DIKTYO_E09_16-01_1900.mp4',
  257. 'title': 'md5:914f06a73cd8b62fbcd6fb90c636e497',
  258. 'ext': 'mp4',
  259. 'thumbnail': 'https://program.ert.gr/photos/2022/1/to_diktio_ep09_i_istoria_tou_diadiktiou_stin_Ellada_1021x576.jpg',
  260. },
  261. }]
  262. def _real_extract(self, url):
  263. video_id = self._match_id(url)
  264. formats, subs = self._extract_m3u8_formats_and_subtitles(
  265. f'https://mediastream.ert.gr/vodedge/_definst_/mp4:dvrorigin/{video_id}/playlist.m3u8',
  266. video_id, 'mp4')
  267. thumbnail_id = parse_qs(url).get('bgimg', [None])[0]
  268. if thumbnail_id and not thumbnail_id.startswith('http'):
  269. thumbnail_id = f'https://program.ert.gr{thumbnail_id}'
  270. return {
  271. 'id': video_id,
  272. 'title': f'VOD - {video_id}',
  273. 'thumbnail': thumbnail_id,
  274. 'formats': formats,
  275. 'subtitles': subs,
  276. }