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