logo

youtube-dl

[mirror] Download/Watch videos from video hostersgit clone https://hacktivis.me/git/mirror/youtube-dl.git

kakao.py (5493B)


  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. from .common import InfoExtractor
  4. from ..compat import compat_HTTPError
  5. from ..utils import (
  6. ExtractorError,
  7. int_or_none,
  8. str_or_none,
  9. strip_or_none,
  10. try_get,
  11. unified_timestamp,
  12. update_url_query,
  13. )
  14. class KakaoIE(InfoExtractor):
  15. _VALID_URL = r'https?://(?:play-)?tv\.kakao\.com/(?:channel/\d+|embed/player)/cliplink/(?P<id>\d+|[^?#&]+@my)'
  16. _API_BASE_TMPL = 'http://tv.kakao.com/api/v1/ft/cliplinks/%s/'
  17. _TESTS = [{
  18. 'url': 'http://tv.kakao.com/channel/2671005/cliplink/301965083',
  19. 'md5': '702b2fbdeb51ad82f5c904e8c0766340',
  20. 'info_dict': {
  21. 'id': '301965083',
  22. 'ext': 'mp4',
  23. 'title': '乃木坂46 バナナマン 「3期生紹介コーナーが始動!顔高低差GPも!」 『乃木坂工事中』',
  24. 'uploader_id': '2671005',
  25. 'uploader': '그랑그랑이',
  26. 'timestamp': 1488160199,
  27. 'upload_date': '20170227',
  28. }
  29. }, {
  30. 'url': 'http://tv.kakao.com/channel/2653210/cliplink/300103180',
  31. 'md5': 'a8917742069a4dd442516b86e7d66529',
  32. 'info_dict': {
  33. 'id': '300103180',
  34. 'ext': 'mp4',
  35. 'description': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)\r\n\r\n[쇼! 음악중심] 20160611, 507회',
  36. 'title': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)',
  37. 'uploader_id': '2653210',
  38. 'uploader': '쇼! 음악중심',
  39. 'timestamp': 1485684628,
  40. 'upload_date': '20170129',
  41. }
  42. }, {
  43. # geo restricted
  44. 'url': 'https://tv.kakao.com/channel/3643855/cliplink/412069491',
  45. 'only_matching': True,
  46. }]
  47. def _real_extract(self, url):
  48. video_id = self._match_id(url)
  49. display_id = video_id.rstrip('@my')
  50. api_base = self._API_BASE_TMPL % video_id
  51. player_header = {
  52. 'Referer': update_url_query(
  53. 'http://tv.kakao.com/embed/player/cliplink/%s' % video_id, {
  54. 'service': 'kakao_tv',
  55. 'autoplay': '1',
  56. 'profile': 'HIGH',
  57. 'wmode': 'transparent',
  58. })
  59. }
  60. query = {
  61. 'player': 'monet_html5',
  62. 'referer': url,
  63. 'uuid': '',
  64. 'service': 'kakao_tv',
  65. 'section': '',
  66. 'dteType': 'PC',
  67. 'fields': ','.join([
  68. '-*', 'tid', 'clipLink', 'displayTitle', 'clip', 'title',
  69. 'description', 'channelId', 'createTime', 'duration', 'playCount',
  70. 'likeCount', 'commentCount', 'tagList', 'channel', 'name', 'thumbnailUrl',
  71. 'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label'])
  72. }
  73. impress = self._download_json(
  74. api_base + 'impress', display_id, 'Downloading video info',
  75. query=query, headers=player_header)
  76. clip_link = impress['clipLink']
  77. clip = clip_link['clip']
  78. title = clip.get('title') or clip_link.get('displayTitle')
  79. query.update({
  80. 'fields': '-*,code,message,url',
  81. 'tid': impress.get('tid') or '',
  82. })
  83. formats = []
  84. for fmt in (clip.get('videoOutputList') or []):
  85. try:
  86. profile_name = fmt['profile']
  87. if profile_name == 'AUDIO':
  88. continue
  89. query['profile'] = profile_name
  90. try:
  91. fmt_url_json = self._download_json(
  92. api_base + 'raw/videolocation', display_id,
  93. 'Downloading video URL for profile %s' % profile_name,
  94. query=query, headers=player_header)
  95. except ExtractorError as e:
  96. if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
  97. resp = self._parse_json(e.cause.read().decode(), video_id)
  98. if resp.get('code') == 'GeoBlocked':
  99. self.raise_geo_restricted()
  100. continue
  101. fmt_url = fmt_url_json['url']
  102. formats.append({
  103. 'url': fmt_url,
  104. 'format_id': profile_name,
  105. 'width': int_or_none(fmt.get('width')),
  106. 'height': int_or_none(fmt.get('height')),
  107. 'format_note': fmt.get('label'),
  108. 'filesize': int_or_none(fmt.get('filesize')),
  109. 'tbr': int_or_none(fmt.get('kbps')),
  110. })
  111. except KeyError:
  112. pass
  113. self._sort_formats(formats)
  114. return {
  115. 'id': display_id,
  116. 'title': title,
  117. 'description': strip_or_none(clip.get('description')),
  118. 'uploader': try_get(clip_link, lambda x: x['channel']['name']),
  119. 'uploader_id': str_or_none(clip_link.get('channelId')),
  120. 'thumbnail': clip.get('thumbnailUrl'),
  121. 'timestamp': unified_timestamp(clip_link.get('createTime')),
  122. 'duration': int_or_none(clip.get('duration')),
  123. 'view_count': int_or_none(clip.get('playCount')),
  124. 'like_count': int_or_none(clip.get('likeCount')),
  125. 'comment_count': int_or_none(clip.get('commentCount')),
  126. 'formats': formats,
  127. 'tags': clip.get('tagList'),
  128. }