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