watchbox.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. from .common import InfoExtractor
  2. from ..compat import compat_str
  3. from ..utils import (
  4. int_or_none,
  5. js_to_json,
  6. strip_or_none,
  7. try_get,
  8. unescapeHTML,
  9. unified_timestamp,
  10. )
  11. class WatchBoxIE(InfoExtractor):
  12. _VALID_URL = r'https?://(?:www\.)?watchbox\.de/(?P<kind>serien|filme)/(?:[^/]+/)*[^/]+-(?P<id>\d+)'
  13. _TESTS = [{
  14. # film
  15. 'url': 'https://www.watchbox.de/filme/free-jimmy-12325.html',
  16. 'info_dict': {
  17. 'id': '341368',
  18. 'ext': 'mp4',
  19. 'title': 'Free Jimmy',
  20. 'description': 'md5:bcd8bafbbf9dc0ef98063d344d7cc5f6',
  21. 'thumbnail': r're:^https?://.*\.jpg$',
  22. 'duration': 4890,
  23. 'age_limit': 16,
  24. 'release_year': 2009,
  25. },
  26. 'params': {
  27. 'skip_download': True,
  28. },
  29. 'expected_warnings': ['Failed to download m3u8 information'],
  30. }, {
  31. # episode
  32. 'url': 'https://www.watchbox.de/serien/ugly-americans-12231/staffel-1/date-in-der-hoelle-328286.html',
  33. 'info_dict': {
  34. 'id': '328286',
  35. 'ext': 'mp4',
  36. 'title': 'S01 E01 - Date in der Hölle',
  37. 'description': 'md5:2f31c74a8186899f33cb5114491dae2b',
  38. 'thumbnail': r're:^https?://.*\.jpg$',
  39. 'duration': 1291,
  40. 'age_limit': 12,
  41. 'release_year': 2010,
  42. 'series': 'Ugly Americans',
  43. 'season_number': 1,
  44. 'episode': 'Date in der Hölle',
  45. 'episode_number': 1,
  46. },
  47. 'params': {
  48. 'skip_download': True,
  49. },
  50. 'expected_warnings': ['Failed to download m3u8 information'],
  51. }, {
  52. 'url': 'https://www.watchbox.de/serien/ugly-americans-12231/staffel-2/der-ring-des-powers-328270',
  53. 'only_matching': True,
  54. }]
  55. def _real_extract(self, url):
  56. mobj = self._match_valid_url(url)
  57. kind, video_id = mobj.group('kind', 'id')
  58. webpage = self._download_webpage(url, video_id)
  59. player_config = self._parse_json(
  60. self._search_regex(
  61. r'data-player-conf=(["\'])(?P<data>{.+?})\1', webpage,
  62. 'player config', default='{}', group='data'),
  63. video_id, transform_source=unescapeHTML, fatal=False)
  64. if not player_config:
  65. player_config = self._parse_json(
  66. self._search_regex(
  67. r'playerConf\s*=\s*({.+?})\s*;', webpage, 'player config',
  68. default='{}'),
  69. video_id, transform_source=js_to_json, fatal=False) or {}
  70. source = player_config.get('source') or {}
  71. video_id = compat_str(source.get('videoId') or video_id)
  72. devapi = self._download_json(
  73. 'http://api.watchbox.de/devapi/id/%s' % video_id, video_id, query={
  74. 'format': 'json',
  75. 'apikey': 'hbbtv',
  76. }, fatal=False)
  77. item = try_get(devapi, lambda x: x['items'][0], dict) or {}
  78. title = item.get('title') or try_get(
  79. item, lambda x: x['movie']['headline_movie'],
  80. compat_str) or source['title']
  81. formats = []
  82. hls_url = item.get('media_videourl_hls') or source.get('hls')
  83. if hls_url:
  84. formats.extend(self._extract_m3u8_formats(
  85. hls_url, video_id, 'mp4', entry_protocol='m3u8_native',
  86. m3u8_id='hls', fatal=False))
  87. dash_url = item.get('media_videourl_wv') or source.get('dash')
  88. if dash_url:
  89. formats.extend(self._extract_mpd_formats(
  90. dash_url, video_id, mpd_id='dash', fatal=False))
  91. mp4_url = item.get('media_videourl')
  92. if mp4_url:
  93. formats.append({
  94. 'url': mp4_url,
  95. 'format_id': 'mp4',
  96. 'width': int_or_none(item.get('width')),
  97. 'height': int_or_none(item.get('height')),
  98. 'tbr': int_or_none(item.get('bitrate')),
  99. })
  100. description = strip_or_none(item.get('descr'))
  101. thumbnail = item.get('media_content_thumbnail_large') or source.get('poster') or item.get('media_thumbnail')
  102. duration = int_or_none(item.get('media_length') or source.get('length'))
  103. timestamp = unified_timestamp(item.get('pubDate'))
  104. view_count = int_or_none(item.get('media_views'))
  105. age_limit = int_or_none(try_get(item, lambda x: x['movie']['fsk']))
  106. release_year = int_or_none(try_get(item, lambda x: x['movie']['rel_year']))
  107. info = {
  108. 'id': video_id,
  109. 'title': title,
  110. 'description': description,
  111. 'thumbnail': thumbnail,
  112. 'duration': duration,
  113. 'timestamp': timestamp,
  114. 'view_count': view_count,
  115. 'age_limit': age_limit,
  116. 'release_year': release_year,
  117. 'formats': formats,
  118. }
  119. if kind.lower() == 'serien':
  120. series = try_get(
  121. item, lambda x: x['special']['title'],
  122. compat_str) or source.get('format')
  123. season_number = int_or_none(self._search_regex(
  124. r'^S(\d{1,2})\s*E\d{1,2}', title, 'season number',
  125. default=None) or self._search_regex(
  126. r'/staffel-(\d+)/', url, 'season number', default=None))
  127. episode = source.get('title')
  128. episode_number = int_or_none(self._search_regex(
  129. r'^S\d{1,2}\s*E(\d{1,2})', title, 'episode number',
  130. default=None))
  131. info.update({
  132. 'series': series,
  133. 'season_number': season_number,
  134. 'episode': episode,
  135. 'episode_number': episode_number,
  136. })
  137. return info