]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/ichinanalive.py
[misc] Add `hatch`, `ruff`, `pre-commit` and improve dev docs (#7409)
[yt-dlp.git] / yt_dlp / extractor / ichinanalive.py
1 from .common import InfoExtractor
2 from ..compat import compat_str
3 from ..utils import ExtractorError, str_or_none, traverse_obj, unified_strdate
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 return {
77 'id': video_id,
78 'title': uploader or video_id,
79 'formats': formats,
80 'is_live': True,
81 'uploader': uploader,
82 'uploader_id': video_id,
83 'like_count': view_data.get('receivedLikeCount'),
84 'view_count': view_data.get('viewerCount'),
85 'thumbnail': view_data.get('coverPhoto'),
86 'description': view_data.get('caption'),
87 'timestamp': view_data.get('beginTime'),
88 }
89
90
91 class IchinanaLiveClipIE(InfoExtractor):
92 IE_NAME = '17live:clip'
93 _VALID_URL = r'https?://(?:www\.)?17\.live/(?:[^/]+/)*profile/r/(?P<uploader_id>\d+)/clip/(?P<id>[^/]+)'
94 _TESTS = [{
95 'url': 'https://17.live/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
96 'info_dict': {
97 'id': '1bHQSK8KUieruFXaCH4A4upCzlN',
98 'title': 'マチコ先生🦋Class💋',
99 'description': 'マチ戦隊 第一次 バスターコール\n総額200万coin!\n動画制作@うぉーかー🌱Walker🎫',
100 'uploader_id': '1789280',
101 },
102 }, {
103 'url': 'https://17.live/ja/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
104 'only_matching': True,
105 }]
106
107 def _real_extract(self, url):
108 uploader_id, video_id = self._match_valid_url(url).groups()
109 url = 'https://17.live/profile/r/%s/clip/%s' % (uploader_id, video_id)
110
111 view_data = self._download_json(
112 'https://api-dsa.17app.co/api/v1/clips/%s' % video_id, video_id,
113 headers={'Referer': url})
114
115 uploader = traverse_obj(
116 view_data, ('userInfo', 'displayName'), ('userInfo', 'name'))
117
118 formats = []
119 if view_data.get('videoURL'):
120 formats.append({
121 'id': 'video',
122 'url': view_data['videoURL'],
123 'quality': -1,
124 })
125 if view_data.get('transcodeURL'):
126 formats.append({
127 'id': 'transcode',
128 'url': view_data['transcodeURL'],
129 'quality': -1,
130 })
131 if view_data.get('srcVideoURL'):
132 # highest quality
133 formats.append({
134 'id': 'srcVideo',
135 'url': view_data['srcVideoURL'],
136 'quality': 1,
137 })
138
139 for fmt in formats:
140 fmt.update({
141 'ext': 'mp4',
142 'protocol': 'https',
143 'vcodec': 'h264',
144 'acodec': 'aac',
145 'http_headers': {'Referer': url},
146 })
147
148 return {
149 'id': video_id,
150 'title': uploader or video_id,
151 'formats': formats,
152 'uploader': uploader,
153 'uploader_id': uploader_id,
154 'like_count': view_data.get('likeCount'),
155 'view_count': view_data.get('viewCount'),
156 'thumbnail': view_data.get('imageURL'),
157 'duration': view_data.get('duration'),
158 'description': view_data.get('caption'),
159 'upload_date': unified_strdate(str_or_none(view_data.get('createdAt'))),
160 }