]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/ichinanalive.py
[cleanup] Upgrade syntax
[yt-dlp.git] / yt_dlp / extractor / ichinanalive.py
1 from .common import InfoExtractor
2 from ..utils import ExtractorError, str_or_none, traverse_obj, unified_strdate
3 from ..compat import compat_str
4
5
6 class IchinanaLiveIE(InfoExtractor):
7 IE_NAME = '17live'
8 _VALID_URL = r'https?://(?:www\.)?17\.live/(?:[^/]+/)*(?:live|profile/r)/(?P<id>\d+)'
9 _TESTS = [{
10 'url': 'https://17.live/live/3773096',
11 'info_dict': {
12 'id': '3773096',
13 'title': '萠珈☕🤡🍫moka',
14 'is_live': True,
15 'uploader': '萠珈☕🤡🍫moka',
16 'uploader_id': '3773096',
17 'like_count': 366,
18 'view_count': 18121,
19 'timestamp': 1630569012,
20 },
21 'skip': 'running as of writing, but may be ended as of testing',
22 }, {
23 'note': 'nothing except language differs',
24 'url': 'https://17.live/ja/live/3773096',
25 'only_matching': True,
26 }]
27
28 @classmethod
29 def suitable(cls, url):
30 return not IchinanaLiveClipIE.suitable(url) and super(IchinanaLiveIE, cls).suitable(url)
31
32 def _real_extract(self, url):
33 video_id = self._match_id(url)
34 url = 'https://17.live/live/%s' % video_id
35
36 enter = self._download_json(
37 'https://api-dsa.17app.co/api/v1/lives/%s/enter' % video_id, video_id,
38 headers={'Referer': url}, fatal=False, expected_status=420,
39 data=b'\0')
40 if enter and enter.get('message') == 'ended':
41 raise ExtractorError('This live has ended.', expected=True)
42
43 view_data = self._download_json(
44 'https://api-dsa.17app.co/api/v1/lives/%s' % video_id, video_id,
45 headers={'Referer': url})
46
47 uploader = traverse_obj(
48 view_data, ('userInfo', 'displayName'), ('userInfo', 'openID'))
49
50 video_urls = view_data.get('rtmpUrls')
51 if not video_urls:
52 raise ExtractorError('unable to extract live URL information')
53 formats = []
54 for (name, value) in video_urls[0].items():
55 if not isinstance(value, compat_str):
56 continue
57 if not value.startswith('http'):
58 continue
59 quality = -1
60 if 'web' in name:
61 quality -= 1
62 if 'High' in name:
63 quality += 4
64 if 'Low' in name:
65 quality -= 2
66 formats.append({
67 'format_id': name,
68 'url': value,
69 'quality': quality,
70 'http_headers': {'Referer': url},
71 'ext': 'flv',
72 'vcodec': 'h264',
73 'acodec': 'aac',
74 })
75
76 self._sort_formats(formats)
77
78 return {
79 'id': video_id,
80 'title': uploader or video_id,
81 'formats': formats,
82 'is_live': True,
83 'uploader': uploader,
84 'uploader_id': video_id,
85 'like_count': view_data.get('receivedLikeCount'),
86 'view_count': view_data.get('viewerCount'),
87 'thumbnail': view_data.get('coverPhoto'),
88 'description': view_data.get('caption'),
89 'timestamp': view_data.get('beginTime'),
90 }
91
92
93 class IchinanaLiveClipIE(InfoExtractor):
94 IE_NAME = '17live:clip'
95 _VALID_URL = r'https?://(?:www\.)?17\.live/(?:[^/]+/)*profile/r/(?P<uploader_id>\d+)/clip/(?P<id>[^/]+)'
96 _TESTS = [{
97 'url': 'https://17.live/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
98 'info_dict': {
99 'id': '1bHQSK8KUieruFXaCH4A4upCzlN',
100 'title': 'マチコ先生🦋Class💋',
101 'description': 'マチ戦隊 第一次 バスターコール\n総額200万coin!\n動画制作@うぉーかー🌱Walker🎫',
102 'uploader_id': '1789280',
103 },
104 }, {
105 'url': 'https://17.live/ja/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
106 'only_matching': True,
107 }]
108
109 def _real_extract(self, url):
110 uploader_id, video_id = self._match_valid_url(url).groups()
111 url = 'https://17.live/profile/r/%s/clip/%s' % (uploader_id, video_id)
112
113 view_data = self._download_json(
114 'https://api-dsa.17app.co/api/v1/clips/%s' % video_id, video_id,
115 headers={'Referer': url})
116
117 uploader = traverse_obj(
118 view_data, ('userInfo', 'displayName'), ('userInfo', 'name'))
119
120 formats = []
121 if view_data.get('videoURL'):
122 formats.append({
123 'id': 'video',
124 'url': view_data['videoURL'],
125 'quality': -1,
126 })
127 if view_data.get('transcodeURL'):
128 formats.append({
129 'id': 'transcode',
130 'url': view_data['transcodeURL'],
131 'quality': -1,
132 })
133 if view_data.get('srcVideoURL'):
134 # highest quality
135 formats.append({
136 'id': 'srcVideo',
137 'url': view_data['srcVideoURL'],
138 'quality': 1,
139 })
140
141 for fmt in formats:
142 fmt.update({
143 'ext': 'mp4',
144 'protocol': 'https',
145 'vcodec': 'h264',
146 'acodec': 'aac',
147 'http_headers': {'Referer': url},
148 })
149
150 self._sort_formats(formats)
151
152 return {
153 'id': video_id,
154 'title': uploader or video_id,
155 'formats': formats,
156 'uploader': uploader,
157 'uploader_id': uploader_id,
158 'like_count': view_data.get('likeCount'),
159 'view_count': view_data.get('viewCount'),
160 'thumbnail': view_data.get('imageURL'),
161 'duration': view_data.get('duration'),
162 'description': view_data.get('caption'),
163 'upload_date': unified_strdate(str_or_none(view_data.get('createdAt'))),
164 }