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