idolplus.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from .common import InfoExtractor
  2. from ..utils import traverse_obj, try_call, url_or_none
  3. class IdolPlusIE(InfoExtractor):
  4. _VALID_URL = r'https?://(?:www\.)?idolplus\.com/z[us]/(?:concert/|contents/?\?(?:[^#]+&)?albumId=)(?P<id>\w+)'
  5. _TESTS = [{
  6. 'url': 'https://idolplus.com/zs/contents?albumId=M012077298PPV00',
  7. 'md5': '2ace3f4661c943a2f7e79f0b88cea1e7',
  8. 'info_dict': {
  9. 'id': 'M012077298PPV00',
  10. 'ext': 'mp4',
  11. 'title': '[MultiCam] Aegyo on Top of Aegyo (IZ*ONE EATING TRIP)',
  12. 'release_date': '20200707',
  13. 'formats': 'count:65',
  14. },
  15. 'params': {'format': '532-KIM_MINJU'},
  16. }, {
  17. 'url': 'https://idolplus.com/zs/contents?albumId=M01232H058PPV00&catId=E9TX5',
  18. 'info_dict': {
  19. 'id': 'M01232H058PPV00',
  20. 'ext': 'mp4',
  21. 'title': 'YENA (CIRCLE CHART MUSIC AWARDS 2022 RED CARPET)',
  22. 'release_date': '20230218',
  23. 'formats': 'count:5',
  24. },
  25. 'params': {'skip_download': 'm3u8'},
  26. }, {
  27. # live stream
  28. 'url': 'https://idolplus.com/zu/contents?albumId=M012323174PPV00',
  29. 'info_dict': {
  30. 'id': 'M012323174PPV00',
  31. 'ext': 'mp4',
  32. 'title': 'Hanteo Music Awards 2022 DAY2',
  33. 'release_date': '20230211',
  34. 'formats': 'count:5',
  35. },
  36. 'params': {'skip_download': 'm3u8'},
  37. }, {
  38. 'url': 'https://idolplus.com/zs/concert/M012323039PPV00',
  39. 'info_dict': {
  40. 'id': 'M012323039PPV00',
  41. 'ext': 'mp4',
  42. 'title': 'CIRCLE CHART MUSIC AWARDS 2022',
  43. 'release_date': '20230218',
  44. 'formats': 'count:5',
  45. },
  46. 'params': {'skip_download': 'm3u8'},
  47. }]
  48. def _real_extract(self, url):
  49. video_id = self._match_id(url)
  50. data_list = traverse_obj(self._download_json(
  51. 'https://idolplus.com/api/zs/viewdata/ruleset/build', video_id,
  52. headers={'App_type': 'web', 'Country_Code': 'KR'}, query={
  53. 'rulesetId': 'contents',
  54. 'albumId': video_id,
  55. 'distribute': 'PRD',
  56. 'loggedIn': 'false',
  57. 'region': 'zs',
  58. 'countryGroup': '00010',
  59. 'lang': 'en',
  60. 'saId': '999999999998',
  61. }), ('data', 'viewData', ...))
  62. player_data = {}
  63. while data_list:
  64. player_data = data_list.pop()
  65. if traverse_obj(player_data, 'type') == 'player':
  66. break
  67. elif traverse_obj(player_data, ('dataList', ...)):
  68. data_list += player_data['dataList']
  69. formats = self._extract_m3u8_formats(traverse_obj(player_data, (
  70. 'vodPlayerList', 'vodProfile', 0, 'vodServer', 0, 'video_url', {url_or_none})), video_id)
  71. subtitles = {}
  72. for caption in traverse_obj(player_data, ('vodPlayerList', 'caption')) or []:
  73. subtitles.setdefault(caption.get('lang') or 'und', []).append({
  74. 'url': caption.get('smi_url'),
  75. 'ext': 'vtt',
  76. })
  77. # Add member multicams as alternative formats
  78. if (traverse_obj(player_data, ('detail', 'has_cuesheet')) == 'Y'
  79. and traverse_obj(player_data, ('detail', 'is_omni_member')) == 'Y'):
  80. cuesheet = traverse_obj(self._download_json(
  81. 'https://idolplus.com/gapi/contents/v1.0/content/cuesheet', video_id,
  82. 'Downloading JSON metadata for member multicams',
  83. headers={'App_type': 'web', 'Country_Code': 'KR'}, query={
  84. 'ALBUM_ID': video_id,
  85. 'COUNTRY_GRP': '00010',
  86. 'LANG': 'en',
  87. 'SA_ID': '999999999998',
  88. 'COUNTRY_CODE': 'KR',
  89. }), ('data', 'cuesheet_item', 0))
  90. for member in traverse_obj(cuesheet, ('members', ...)):
  91. index = try_call(lambda: int(member['omni_view_index']) - 1)
  92. member_video_url = traverse_obj(cuesheet, ('omni_view', index, 'cdn_url', 0, 'url', {url_or_none}))
  93. if not member_video_url:
  94. continue
  95. member_formats = self._extract_m3u8_formats(
  96. member_video_url, video_id, note=f'Downloading m3u8 for multicam {member["name"]}')
  97. for mf in member_formats:
  98. mf['format_id'] = f'{mf["format_id"]}-{member["name"].replace(" ", "_")}'
  99. formats.extend(member_formats)
  100. return {
  101. 'id': video_id,
  102. 'title': traverse_obj(player_data, ('detail', 'albumName')),
  103. 'formats': formats,
  104. 'subtitles': subtitles,
  105. 'release_date': traverse_obj(player_data, ('detail', 'broadcastDate')),
  106. }