1 from .common
import InfoExtractor
2 from ..networking
.exceptions
import HTTPError
13 class KakaoIE(InfoExtractor
):
14 _VALID_URL
= r
'https?://(?:play-)?tv\.kakao\.com/(?:channel/\d+|embed/player)/cliplink/(?P<id>\d+|[^?#&]+@my)'
15 _API_BASE_TMPL
= 'http://tv.kakao.com/api/v1/ft/playmeta/cliplink/%s/'
16 _CDN_API
= 'https://tv.kakao.com/katz/v1/ft/cliplink/%s/readyNplay?'
19 'url': 'http://tv.kakao.com/channel/2671005/cliplink/301965083',
20 'md5': '702b2fbdeb51ad82f5c904e8c0766340',
24 'title': '乃木坂46 バナナマン 「3期生紹介コーナーが始動!顔高低差GPも!」 『乃木坂工事中』',
26 'uploader_id': '2671005',
28 'timestamp': 1488160199,
29 'upload_date': '20170227',
31 'thumbnail': r
're:http://.+/thumb\.png',
38 'url': 'http://tv.kakao.com/channel/2653210/cliplink/300103180',
39 'md5': 'a8917742069a4dd442516b86e7d66529',
43 'description': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)\r\n\r\n[쇼! 음악중심] 20160611, 507회',
44 'title': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)',
45 'uploader_id': '2653210',
46 'uploader': '쇼! 음악중심',
47 'timestamp': 1485684628,
48 'upload_date': '20170129',
50 'thumbnail': r
're:http://.+/thumb\.png',
58 'url': 'https://tv.kakao.com/channel/3643855/cliplink/412069491',
59 'only_matching': True,
62 def _real_extract(self
, url
):
63 video_id
= self
._match
_id
(url
)
64 api_base
= self
._API
_BASE
_TMPL
% video_id
65 cdn_api_base
= self
._CDN
_API
% video_id
68 'player': 'monet_html5',
71 'service': 'kakao_tv',
75 '-*', 'tid', 'clipLink', 'displayTitle', 'clip', 'title',
76 'description', 'channelId', 'createTime', 'duration', 'playCount',
77 'likeCount', 'commentCount', 'tagList', 'channel', 'name',
78 'clipChapterThumbnailList', 'thumbnailUrl', 'timeInSec', 'isDefault',
79 'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label']),
82 api_json
= self
._download
_json
(
83 api_base
, video_id
, 'Downloading video info')
85 clip_link
= api_json
['clipLink']
86 clip
= clip_link
['clip']
88 title
= clip
.get('title') or clip_link
.get('displayTitle')
91 for fmt
in clip
.get('videoOutputList') or []:
92 profile_name
= fmt
.get('profile')
93 if not profile_name
or profile_name
== 'AUDIO':
96 'profile': profile_name
,
97 'fields': '-*,code,message,url',
100 fmt_url_json
= self
._download
_json
(
101 cdn_api_base
, video_id
, query
=query
,
102 note
=f
'Downloading video URL for profile {profile_name}')
103 except ExtractorError
as e
:
104 if isinstance(e
.cause
, HTTPError
) and e
.cause
.status
== 403:
105 resp
= self
._parse
_json
(e
.cause
.response
.read().decode(), video_id
)
106 if resp
.get('code') == 'GeoBlocked':
107 self
.raise_geo_restricted()
110 fmt_url
= traverse_obj(fmt_url_json
, ('videoLocation', 'url'))
116 'format_id': profile_name
,
117 'width': int_or_none(fmt
.get('width')),
118 'height': int_or_none(fmt
.get('height')),
119 'format_note': fmt
.get('label'),
120 'filesize': int_or_none(fmt
.get('filesize')),
121 'tbr': int_or_none(fmt
.get('kbps')),
125 for thumb
in clip
.get('clipChapterThumbnailList') or []:
127 'url': thumb
.get('thumbnailUrl'),
128 'id': str(thumb
.get('timeInSec')),
129 'preference': -1 if thumb
.get('isDefault') else 0,
131 top_thumbnail
= clip
.get('thumbnailUrl')
134 'url': top_thumbnail
,
141 'description': strip_or_none(clip
.get('description')),
142 'uploader': traverse_obj(clip_link
, ('channel', 'name')),
143 'uploader_id': str_or_none(clip_link
.get('channelId')),
144 'thumbnails': thumbs
,
145 'timestamp': unified_timestamp(clip_link
.get('createTime')),
146 'duration': int_or_none(clip
.get('duration')),
147 'view_count': int_or_none(clip
.get('playCount')),
148 'like_count': int_or_none(clip
.get('likeCount')),
149 'comment_count': int_or_none(clip
.get('commentCount')),
151 'tags': clip
.get('tagList'),