telewebion.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. from __future__ import annotations
  2. import functools
  3. import json
  4. import textwrap
  5. from .common import InfoExtractor
  6. from ..utils import ExtractorError, format_field, int_or_none, parse_iso8601
  7. from ..utils.traversal import traverse_obj
  8. def _fmt_url(url):
  9. return functools.partial(format_field, template=url, default=None)
  10. class TelewebionIE(InfoExtractor):
  11. _VALID_URL = r'https?://(?:www\.)?telewebion\.com/episode/(?P<id>(?:0x[a-fA-F\d]+|\d+))'
  12. _TESTS = [{
  13. 'url': 'http://www.telewebion.com/episode/0x1b3139c/',
  14. 'info_dict': {
  15. 'id': '0x1b3139c',
  16. 'ext': 'mp4',
  17. 'title': 'قرعه‌کشی لیگ قهرمانان اروپا',
  18. 'series': '+ فوتبال',
  19. 'series_id': '0x1b2505c',
  20. 'channel': 'شبکه 3',
  21. 'channel_id': '0x1b1a761',
  22. 'channel_url': 'https://telewebion.com/live/tv3',
  23. 'timestamp': 1425522414,
  24. 'upload_date': '20150305',
  25. 'release_timestamp': 1425517020,
  26. 'release_date': '20150305',
  27. 'duration': 420,
  28. 'view_count': int,
  29. 'tags': ['ورزشی', 'لیگ اروپا', 'اروپا'],
  30. 'thumbnail': 'https://static.telewebion.com/episodeImages/YjFhM2MxMDBkMDNiZTU0MjE5YjQ3ZDY0Mjk1ZDE0ZmUwZWU3OTE3OWRmMDAyODNhNzNkNjdmMWMzMWIyM2NmMA/default',
  31. },
  32. 'skip_download': 'm3u8',
  33. }, {
  34. 'url': 'https://telewebion.com/episode/162175536',
  35. 'info_dict': {
  36. 'id': '0x9aa9a30',
  37. 'ext': 'mp4',
  38. 'title': 'کارما یعنی این !',
  39. 'series': 'پاورقی',
  40. 'series_id': '0x29a7426',
  41. 'channel': 'شبکه 2',
  42. 'channel_id': '0x1b1a719',
  43. 'channel_url': 'https://telewebion.com/live/tv2',
  44. 'timestamp': 1699979968,
  45. 'upload_date': '20231114',
  46. 'release_timestamp': 1699991638,
  47. 'release_date': '20231114',
  48. 'duration': 78,
  49. 'view_count': int,
  50. 'tags': ['کلیپ های منتخب', ' کلیپ طنز ', ' کلیپ سیاست ', 'پاورقی', 'ویژه فلسطین'],
  51. 'thumbnail': 'https://static.telewebion.com/episodeImages/871e9455-7567-49a5-9648-34c22c197f5f/default',
  52. },
  53. 'skip_download': 'm3u8',
  54. }]
  55. def _call_graphql_api(
  56. self, operation, video_id, query,
  57. variables: dict[str, tuple[str, str]] | None = None,
  58. note='Downloading GraphQL JSON metadata',
  59. ):
  60. parameters = ''
  61. if variables:
  62. parameters = ', '.join(f'${name}: {type_}' for name, (type_, _) in variables.items())
  63. parameters = f'({parameters})'
  64. result = self._download_json('https://graph.telewebion.com/graphql', video_id, note, data=json.dumps({
  65. 'operationName': operation,
  66. 'query': f'query {operation}{parameters} @cacheControl(maxAge: 60) {{{query}\n}}\n',
  67. 'variables': {name: value for name, (_, value) in (variables or {}).items()},
  68. }, separators=(',', ':')).encode(), headers={
  69. 'Content-Type': 'application/json',
  70. 'Accept': 'application/json',
  71. })
  72. if not result or traverse_obj(result, 'errors'):
  73. message = ', '.join(traverse_obj(result, ('errors', ..., 'message', {str})))
  74. raise ExtractorError(message or 'Unknown GraphQL API error')
  75. return result['data']
  76. def _real_extract(self, url):
  77. video_id = self._match_id(url)
  78. if not video_id.startswith('0x'):
  79. video_id = hex(int(video_id))
  80. episode_data = self._call_graphql_api('getEpisodeDetail', video_id, textwrap.dedent('''
  81. queryEpisode(filter: {EpisodeID: $EpisodeId}, first: 1) {
  82. title
  83. program {
  84. ProgramID
  85. title
  86. }
  87. image
  88. view_count
  89. duration
  90. started_at
  91. created_at
  92. channel {
  93. ChannelID
  94. name
  95. descriptor
  96. }
  97. tags {
  98. name
  99. }
  100. }
  101. '''), {'EpisodeId': ('[ID!]', video_id)})
  102. info_dict = traverse_obj(episode_data, ('queryEpisode', 0, {
  103. 'title': ('title', {str}),
  104. 'view_count': ('view_count', {int_or_none}),
  105. 'duration': ('duration', {int_or_none}),
  106. 'tags': ('tags', ..., 'name', {str}),
  107. 'release_timestamp': ('started_at', {parse_iso8601}),
  108. 'timestamp': ('created_at', {parse_iso8601}),
  109. 'series': ('program', 'title', {str}),
  110. 'series_id': ('program', 'ProgramID', {str}),
  111. 'channel': ('channel', 'name', {str}),
  112. 'channel_id': ('channel', 'ChannelID', {str}),
  113. 'channel_url': ('channel', 'descriptor', {_fmt_url('https://telewebion.com/live/%s')}),
  114. 'thumbnail': ('image', {_fmt_url('https://static.telewebion.com/episodeImages/%s/default')}),
  115. 'formats': (
  116. 'channel', 'descriptor', {str},
  117. {_fmt_url(f'https://cdna.telewebion.com/%s/episode/{video_id}/playlist.m3u8')},
  118. {functools.partial(self._extract_m3u8_formats, video_id=video_id, ext='mp4', m3u8_id='hls')}),
  119. }))
  120. info_dict['id'] = video_id
  121. return info_dict