err.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from .common import InfoExtractor
  2. from ..utils import (
  3. clean_html,
  4. int_or_none,
  5. str_or_none,
  6. url_or_none,
  7. )
  8. from ..utils.traversal import traverse_obj
  9. class ERRJupiterIE(InfoExtractor):
  10. _VALID_URL = r'https?://(?:jupiter(?:pluss)?|lasteekraan)\.err\.ee/(?P<id>\d+)'
  11. _TESTS = [{
  12. 'note': 'Jupiter: Movie: siin-me-oleme',
  13. 'url': 'https://jupiter.err.ee/1211107/siin-me-oleme',
  14. 'md5': '9b45d1682a98853acaa1e1b0c791f425',
  15. 'info_dict': {
  16. 'id': '1211107',
  17. 'ext': 'mp4',
  18. 'title': 'Siin me oleme!',
  19. 'alt_title': '',
  20. 'description': 'md5:1825b795f5f7584241aeb59e5bbb4f70',
  21. 'release_date': '20231226',
  22. 'upload_date': '20201217',
  23. 'modified_date': '20201217',
  24. 'release_timestamp': 1703577600,
  25. 'timestamp': 1608210000,
  26. 'modified_timestamp': 1608220800,
  27. 'release_year': 1978,
  28. },
  29. }, {
  30. 'note': 'Jupiter: Series: Impulss',
  31. 'url': 'https://jupiter.err.ee/1609145945/impulss',
  32. 'md5': 'a378486df07ed1ba74e46cc861886243',
  33. 'info_dict': {
  34. 'id': '1609145945',
  35. 'ext': 'mp4',
  36. 'title': 'Impulss',
  37. 'alt_title': 'Loteriipilet hooldekodusse',
  38. 'description': 'md5:fa8a2ed0cdccb130211513443ee4d571',
  39. 'release_date': '20231107',
  40. 'upload_date': '20231026',
  41. 'modified_date': '20231118',
  42. 'release_timestamp': 1699380000,
  43. 'timestamp': 1698327601,
  44. 'modified_timestamp': 1700311802,
  45. 'series': 'Impulss',
  46. 'season': 'Season 1',
  47. 'season_number': 1,
  48. 'episode': 'Loteriipilet hooldekodusse',
  49. 'episode_number': 6,
  50. 'series_id': '1609108187',
  51. 'release_year': 2023,
  52. 'episode_id': '1609145945',
  53. },
  54. }, {
  55. 'note': 'Jupiter: Radio Show: mnemoturniir episode',
  56. 'url': 'https://jupiter.err.ee/1037919/mnemoturniir',
  57. 'md5': 'f1eb95fe66f9620ff84e81bbac37076a',
  58. 'info_dict': {
  59. 'id': '1037919',
  60. 'ext': 'm4a',
  61. 'title': 'Mnemoturniir',
  62. 'alt_title': '',
  63. 'description': 'md5:626db52394e7583c26ab74d6a34d9982',
  64. 'release_date': '20240121',
  65. 'upload_date': '20240108',
  66. 'modified_date': '20240121',
  67. 'release_timestamp': 1705827900,
  68. 'timestamp': 1704675602,
  69. 'modified_timestamp': 1705827601,
  70. 'series': 'Mnemoturniir',
  71. 'season': 'Season 0',
  72. 'season_number': 0,
  73. 'episode': 'Episode 0',
  74. 'episode_number': 0,
  75. 'series_id': '1037919',
  76. 'release_year': 2024,
  77. 'episode_id': '1609215101',
  78. },
  79. }, {
  80. 'note': 'Jupiter+: Clip: bolee-zelenyj-tallinn',
  81. 'url': 'https://jupiterpluss.err.ee/1609180445/bolee-zelenyj-tallinn',
  82. 'md5': '1b812270c4daf6ce51c06bfeaf33ed95',
  83. 'info_dict': {
  84. 'id': '1609180445',
  85. 'ext': 'mp4',
  86. 'title': 'Более зеленый Таллинн',
  87. 'alt_title': '',
  88. 'description': 'md5:fd34d9bf939c28c4a725b19a7f0d6320',
  89. 'release_date': '20231224',
  90. 'upload_date': '20231130',
  91. 'modified_date': '20231207',
  92. 'release_timestamp': 1703423400,
  93. 'timestamp': 1701338400,
  94. 'modified_timestamp': 1701967200,
  95. 'release_year': 2023,
  96. },
  97. }, {
  98. 'note': 'Jupiter+: Series: The Sniffer',
  99. 'url': 'https://jupiterpluss.err.ee/1608311387/njuhach',
  100. 'md5': '2abdeb7131ce551bce49e8d0cea08536',
  101. 'info_dict': {
  102. 'id': '1608311387',
  103. 'ext': 'mp4',
  104. 'title': 'Нюхач',
  105. 'alt_title': '',
  106. 'description': 'md5:8c5c7d8f32ec6e54cd498c9e59ca83bc',
  107. 'release_date': '20230601',
  108. 'upload_date': '20210818',
  109. 'modified_date': '20210903',
  110. 'release_timestamp': 1685633400,
  111. 'timestamp': 1629318000,
  112. 'modified_timestamp': 1630686000,
  113. 'release_year': 2013,
  114. 'episode': 'Episode 1',
  115. 'episode_id': '1608311390',
  116. 'episode_number': 1,
  117. 'season': 'Season 1',
  118. 'season_number': 1,
  119. 'series': 'Нюхач',
  120. 'series_id': '1608311387',
  121. },
  122. }, {
  123. 'note': 'Jupiter+: Podcast: lesnye-istorii-aisty',
  124. 'url': 'https://jupiterpluss.err.ee/1608990335/lesnye-istorii-aisty',
  125. 'md5': '8b46d7e4510b254a14b7a52211b5bf96',
  126. 'info_dict': {
  127. 'id': '1608990335',
  128. 'ext': 'm4a',
  129. 'title': 'Лесные истории | Аисты',
  130. 'alt_title': '',
  131. 'description': 'md5:065e721623e271e7a63e6540d409ca6b',
  132. 'release_date': '20230609',
  133. 'upload_date': '20230527',
  134. 'modified_date': '20230608',
  135. 'release_timestamp': 1686308700,
  136. 'timestamp': 1685145600,
  137. 'modified_timestamp': 1686252600,
  138. 'release_year': 2023,
  139. 'episode': 'Episode 0',
  140. 'episode_id': '1608990335',
  141. 'episode_number': 0,
  142. 'season': 'Season 0',
  143. 'season_number': 0,
  144. 'series': 'Лесные истории | Аисты',
  145. 'series_id': '1037497',
  146. },
  147. }, {
  148. 'note': 'Lasteekraan: Pätu',
  149. 'url': 'https://lasteekraan.err.ee/1092243/patu',
  150. 'md5': 'a67eb9b9bcb3d201718c15d1638edf77',
  151. 'info_dict': {
  152. 'id': '1092243',
  153. 'ext': 'mp4',
  154. 'title': 'Pätu',
  155. 'alt_title': '',
  156. 'description': 'md5:64a7b5a80afd7042d3f8ec48c77befd9',
  157. 'release_date': '20230614',
  158. 'upload_date': '20200520',
  159. 'modified_date': '20200520',
  160. 'release_timestamp': 1686745800,
  161. 'timestamp': 1589975640,
  162. 'modified_timestamp': 1589975640,
  163. 'release_year': 1990,
  164. 'episode': 'Episode 1',
  165. 'episode_id': '1092243',
  166. 'episode_number': 1,
  167. 'season': 'Season 1',
  168. 'season_number': 1,
  169. 'series': 'Pätu',
  170. 'series_id': '1092236',
  171. },
  172. }]
  173. def _real_extract(self, url):
  174. video_id = self._match_id(url)
  175. data = self._download_json(
  176. 'https://services.err.ee/api/v2/vodContent/getContentPageData', video_id,
  177. query={'contentId': video_id})['data']['mainContent']
  178. media_data = traverse_obj(data, ('medias', ..., {dict}), get_all=False)
  179. if traverse_obj(media_data, ('restrictions', 'drm', {bool})):
  180. self.report_drm(video_id)
  181. formats, subtitles = [], {}
  182. for format_url in set(traverse_obj(media_data, ('src', ('hls', 'hls2', 'hlsNew'), {url_or_none}))):
  183. fmts, subs = self._extract_m3u8_formats_and_subtitles(
  184. format_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
  185. formats.extend(fmts)
  186. self._merge_subtitles(subs, target=subtitles)
  187. for format_url in set(traverse_obj(media_data, ('src', ('dash', 'dashNew'), {url_or_none}))):
  188. fmts, subs = self._extract_mpd_formats_and_subtitles(
  189. format_url, video_id, mpd_id='dash', fatal=False)
  190. formats.extend(fmts)
  191. self._merge_subtitles(subs, target=subtitles)
  192. if format_url := traverse_obj(media_data, ('src', 'file', {url_or_none})):
  193. formats.append({
  194. 'url': format_url,
  195. 'format_id': 'http',
  196. })
  197. return {
  198. 'id': video_id,
  199. 'formats': formats,
  200. 'subtitles': subtitles,
  201. **traverse_obj(data, {
  202. 'title': ('heading', {str}),
  203. 'alt_title': ('subHeading', {str}),
  204. 'description': (('lead', 'body'), {clean_html}, {lambda x: x or None}),
  205. 'timestamp': ('created', {int_or_none}),
  206. 'modified_timestamp': ('updated', {int_or_none}),
  207. 'release_timestamp': (('scheduleStart', 'publicStart'), {int_or_none}),
  208. 'release_year': ('year', {int_or_none}),
  209. }, get_all=False),
  210. **(traverse_obj(data, {
  211. 'series': ('heading', {str}),
  212. 'series_id': ('rootContentId', {str_or_none}),
  213. 'episode': ('subHeading', {str}),
  214. 'season_number': ('season', {int_or_none}),
  215. 'episode_number': ('episode', {int_or_none}),
  216. 'episode_id': ('id', {str_or_none}),
  217. }) if data.get('type') == 'episode' else {}),
  218. }