]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/line.py
[extractor] Standardize `_live_title`
[yt-dlp.git] / yt_dlp / extractor / line.py
CommitLineData
08250b69
CHY
1# coding: utf-8
2from __future__ import unicode_literals
3
08250b69
CHY
4
5from .common import InfoExtractor
f7ad7160 6from ..compat import compat_str
7from ..utils import (
f7ad7160 8 int_or_none,
9 js_to_json,
10 str_or_none,
11)
08250b69
CHY
12
13
14class LineTVIE(InfoExtractor):
15 _VALID_URL = r'https?://tv\.line\.me/v/(?P<id>\d+)_[^/]+-(?P<segment>ep\d+-\d+)'
16
17 _TESTS = [{
18 'url': 'https://tv.line.me/v/793123_goodbye-mrblack-ep1-1/list/69246',
19 'info_dict': {
20 'id': '793123_ep1-1',
21 'ext': 'mp4',
22 'title': 'Goodbye Mr.Black | EP.1-1',
23 'thumbnail': r're:^https?://.*\.jpg$',
24 'duration': 998.509,
25 'view_count': int,
26 },
27 }, {
28 'url': 'https://tv.line.me/v/2587507_%E6%B4%BE%E9%81%A3%E5%A5%B3%E9%86%ABx-ep1-02/list/185245',
29 'only_matching': True,
30 }]
31
32 def _real_extract(self, url):
5ad28e7f 33 series_id, segment = self._match_valid_url(url).groups()
08250b69
CHY
34 video_id = '%s_%s' % (series_id, segment)
35
36 webpage = self._download_webpage(url, video_id)
37
38 player_params = self._parse_json(self._search_regex(
39 r'naver\.WebPlayer\(({[^}]+})\)', webpage, 'player parameters'),
40 video_id, transform_source=js_to_json)
41
42 video_info = self._download_json(
43 'https://global-nvapis.line.me/linetv/rmcnmv/vod_play_videoInfo.json',
44 video_id, query={
45 'videoId': player_params['videoId'],
46 'key': player_params['key'],
47 })
48
49 stream = video_info['streams'][0]
50 extra_query = '?__gda__=' + stream['key']['value']
51 formats = self._extract_m3u8_formats(
52 stream['source'] + extra_query, video_id, ext='mp4',
53 entry_protocol='m3u8_native', m3u8_id='hls')
54
55 for a_format in formats:
56 a_format['url'] += extra_query
57
58 duration = None
59 for video in video_info.get('videos', {}).get('list', []):
60 encoding_option = video.get('encodingOption', {})
61 abr = video['bitrate']['audio']
62 vbr = video['bitrate']['video']
63 tbr = abr + vbr
64 formats.append({
65 'url': video['source'],
66 'format_id': 'http-%d' % int(tbr),
67 'height': encoding_option.get('height'),
68 'width': encoding_option.get('width'),
69 'abr': abr,
70 'vbr': vbr,
71 'filesize': video.get('size'),
72 })
73 if video.get('duration') and duration is None:
74 duration = video['duration']
75
76 self._sort_formats(formats)
77
b7da73eb 78 if formats and not formats[0].get('width'):
08250b69
CHY
79 formats[0]['vcodec'] = 'none'
80
81 title = self._og_search_title(webpage)
82
83 # like_count requires an additional API request https://tv.line.me/api/likeit/getCount
f3672ac5 84
08250b69
CHY
85 return {
86 'id': video_id,
87 'title': title,
88 'formats': formats,
89 'extra_param_to_segment_url': extra_query[1:],
90 'duration': duration,
91 'thumbnails': [{'url': thumbnail['source']}
92 for thumbnail in video_info.get('thumbnails', {}).get('list', [])],
93 'view_count': video_info.get('meta', {}).get('count'),
94 }
f7ad7160 95
96
97class LineLiveBaseIE(InfoExtractor):
98 _API_BASE_URL = 'https://live-api.line-apps.com/web/v4.0/channel/'
99
100 def _parse_broadcast_item(self, item):
101 broadcast_id = compat_str(item['id'])
102 title = item['title']
103 is_live = item.get('isBroadcastingNow')
104
105 thumbnails = []
106 for thumbnail_id, thumbnail_url in (item.get('thumbnailURLs') or {}).items():
107 if not thumbnail_url:
108 continue
109 thumbnails.append({
110 'id': thumbnail_id,
111 'url': thumbnail_url,
112 })
113
114 channel = item.get('channel') or {}
115 channel_id = str_or_none(channel.get('id'))
116
117 return {
118 'id': broadcast_id,
39ca3b5c 119 'title': title,
f7ad7160 120 'thumbnails': thumbnails,
121 'timestamp': int_or_none(item.get('createdAt')),
122 'channel': channel.get('name'),
123 'channel_id': channel_id,
124 'channel_url': 'https://live.line.me/channels/' + channel_id if channel_id else None,
125 'duration': int_or_none(item.get('archiveDuration')),
126 'view_count': int_or_none(item.get('viewerCount')),
127 'comment_count': int_or_none(item.get('chatCount')),
128 'is_live': is_live,
129 }
130
131
132class LineLiveIE(LineLiveBaseIE):
133 _VALID_URL = r'https?://live\.line\.me/channels/(?P<channel_id>\d+)/broadcast/(?P<id>\d+)'
134 _TESTS = [{
135 'url': 'https://live.line.me/channels/4867368/broadcast/16331360',
136 'md5': 'bc931f26bf1d4f971e3b0982b3fab4a3',
137 'info_dict': {
138 'id': '16331360',
139 'title': '振りコピ講座😙😙😙',
140 'ext': 'mp4',
141 'timestamp': 1617095132,
142 'upload_date': '20210330',
143 'channel': '白川ゆめか',
144 'channel_id': '4867368',
145 'view_count': int,
146 'comment_count': int,
147 'is_live': False,
148 }
149 }, {
150 # archiveStatus == 'DELETED'
151 'url': 'https://live.line.me/channels/4778159/broadcast/16378488',
152 'only_matching': True,
153 }]
154
155 def _real_extract(self, url):
5ad28e7f 156 channel_id, broadcast_id = self._match_valid_url(url).groups()
f7ad7160 157 broadcast = self._download_json(
158 self._API_BASE_URL + '%s/broadcast/%s' % (channel_id, broadcast_id),
159 broadcast_id)
160 item = broadcast['item']
161 info = self._parse_broadcast_item(item)
162 protocol = 'm3u8' if info['is_live'] else 'm3u8_native'
163 formats = []
164 for k, v in (broadcast.get(('live' if info['is_live'] else 'archived') + 'HLSURLs') or {}).items():
165 if not v:
166 continue
167 if k == 'abr':
168 formats.extend(self._extract_m3u8_formats(
169 v, broadcast_id, 'mp4', protocol,
170 m3u8_id='hls', fatal=False))
171 continue
172 f = {
173 'ext': 'mp4',
174 'format_id': 'hls-' + k,
175 'protocol': protocol,
176 'url': v,
177 }
178 if not k.isdigit():
179 f['vcodec'] = 'none'
180 formats.append(f)
181 if not formats:
182 archive_status = item.get('archiveStatus')
183 if archive_status != 'ARCHIVED':
b7da73eb 184 self.raise_no_formats('this video has been ' + archive_status.lower(), expected=True)
f7ad7160 185 self._sort_formats(formats)
186 info['formats'] = formats
187 return info
188
189
190class LineLiveChannelIE(LineLiveBaseIE):
191 _VALID_URL = r'https?://live\.line\.me/channels/(?P<id>\d+)(?!/broadcast/\d+)(?:[/?&#]|$)'
192 _TEST = {
193 'url': 'https://live.line.me/channels/5893542',
194 'info_dict': {
195 'id': '5893542',
196 'title': 'いくらちゃん',
197 'description': 'md5:c3a4af801f43b2fac0b02294976580be',
198 },
199 'playlist_mincount': 29
200 }
201
202 def _archived_broadcasts_entries(self, archived_broadcasts, channel_id):
203 while True:
204 for row in (archived_broadcasts.get('rows') or []):
205 share_url = str_or_none(row.get('shareURL'))
206 if not share_url:
207 continue
208 info = self._parse_broadcast_item(row)
209 info.update({
210 '_type': 'url',
211 'url': share_url,
212 'ie_key': LineLiveIE.ie_key(),
213 })
214 yield info
215 if not archived_broadcasts.get('hasNextPage'):
216 return
217 archived_broadcasts = self._download_json(
218 self._API_BASE_URL + channel_id + '/archived_broadcasts',
219 channel_id, query={
220 'lastId': info['id'],
221 })
222
223 def _real_extract(self, url):
224 channel_id = self._match_id(url)
225 channel = self._download_json(self._API_BASE_URL + channel_id, channel_id)
226 return self.playlist_result(
227 self._archived_broadcasts_entries(channel.get('archivedBroadcasts') or {}, channel_id),
228 channel_id, channel.get('title'), channel.get('information'))