]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/minds.py
[cleanup] Misc
[yt-dlp.git] / yt_dlp / extractor / minds.py
1 from .common import InfoExtractor
2 from ..compat import compat_str
3 from ..utils import (
4 clean_html,
5 format_field,
6 int_or_none,
7 str_or_none,
8 strip_or_none,
9 )
10
11
12 class MindsBaseIE(InfoExtractor):
13 _VALID_URL_BASE = r'https?://(?:www\.)?minds\.com/'
14
15 def _call_api(self, path, video_id, resource, query=None):
16 api_url = 'https://www.minds.com/api/' + path
17 token = self._get_cookies(api_url).get('XSRF-TOKEN')
18 return self._download_json(
19 api_url, video_id, 'Downloading %s JSON metadata' % resource, headers={
20 'Referer': 'https://www.minds.com/',
21 'X-XSRF-TOKEN': token.value if token else '',
22 }, query=query)
23
24
25 class MindsIE(MindsBaseIE):
26 IE_NAME = 'minds'
27 _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?:media|newsfeed|archive/view)/(?P<id>[0-9]+)'
28 _TESTS = [{
29 'url': 'https://www.minds.com/media/100000000000086822',
30 'md5': '215a658184a419764852239d4970b045',
31 'info_dict': {
32 'id': '100000000000086822',
33 'ext': 'mp4',
34 'title': 'Minds intro sequence',
35 'thumbnail': r're:https?://.+\.png',
36 'uploader_id': 'ottman',
37 'upload_date': '20130524',
38 'timestamp': 1369404826,
39 'uploader': 'Bill Ottman',
40 'view_count': int,
41 'like_count': int,
42 'dislike_count': int,
43 'tags': ['animation'],
44 'comment_count': int,
45 'license': 'attribution-cc',
46 },
47 }, {
48 # entity.type == 'activity' and empty title
49 'url': 'https://www.minds.com/newsfeed/798025111988506624',
50 'md5': 'b2733a74af78d7fd3f541c4cbbaa5950',
51 'info_dict': {
52 'id': '798022190320226304',
53 'ext': 'mp4',
54 'title': '798022190320226304',
55 'uploader': 'ColinFlaherty',
56 'upload_date': '20180111',
57 'timestamp': 1515639316,
58 'uploader_id': 'ColinFlaherty',
59 },
60 }, {
61 'url': 'https://www.minds.com/archive/view/715172106794442752',
62 'only_matching': True,
63 }, {
64 # youtube perma_url
65 'url': 'https://www.minds.com/newsfeed/1197131838022602752',
66 'only_matching': True,
67 }]
68
69 def _real_extract(self, url):
70 entity_id = self._match_id(url)
71 entity = self._call_api(
72 'v1/entities/entity/' + entity_id, entity_id, 'entity')['entity']
73 if entity.get('type') == 'activity':
74 if entity.get('custom_type') == 'video':
75 video_id = entity['entity_guid']
76 else:
77 return self.url_result(entity['perma_url'])
78 else:
79 assert entity['subtype'] == 'video'
80 video_id = entity_id
81 # 1080p and webm formats available only on the sources array
82 video = self._call_api(
83 'v2/media/video/' + video_id, video_id, 'video')
84
85 formats = []
86 for source in (video.get('sources') or []):
87 src = source.get('src')
88 if not src:
89 continue
90 formats.append({
91 'format_id': source.get('label'),
92 'height': int_or_none(source.get('size')),
93 'url': src,
94 })
95 self._sort_formats(formats)
96
97 entity = video.get('entity') or entity
98 owner = entity.get('ownerObj') or {}
99 uploader_id = owner.get('username')
100
101 tags = entity.get('tags')
102 if tags and isinstance(tags, compat_str):
103 tags = [tags]
104
105 thumbnail = None
106 poster = video.get('poster') or entity.get('thumbnail_src')
107 if poster:
108 urlh = self._request_webpage(poster, video_id, fatal=False)
109 if urlh:
110 thumbnail = urlh.geturl()
111
112 return {
113 'id': video_id,
114 'title': entity.get('title') or video_id,
115 'formats': formats,
116 'description': clean_html(entity.get('description')) or None,
117 'license': str_or_none(entity.get('license')),
118 'timestamp': int_or_none(entity.get('time_created')),
119 'uploader': strip_or_none(owner.get('name')),
120 'uploader_id': uploader_id,
121 'uploader_url': format_field(uploader_id, None, 'https://www.minds.com/%s'),
122 'view_count': int_or_none(entity.get('play:count')),
123 'like_count': int_or_none(entity.get('thumbs:up:count')),
124 'dislike_count': int_or_none(entity.get('thumbs:down:count')),
125 'tags': tags,
126 'comment_count': int_or_none(entity.get('comments:count')),
127 'thumbnail': thumbnail,
128 }
129
130
131 class MindsFeedBaseIE(MindsBaseIE):
132 _PAGE_SIZE = 150
133
134 def _entries(self, feed_id):
135 query = {'limit': self._PAGE_SIZE, 'sync': 1}
136 i = 1
137 while True:
138 data = self._call_api(
139 'v2/feeds/container/%s/videos' % feed_id,
140 feed_id, 'page %s' % i, query)
141 entities = data.get('entities') or []
142 for entity in entities:
143 guid = entity.get('guid')
144 if not guid:
145 continue
146 yield self.url_result(
147 'https://www.minds.com/newsfeed/' + guid,
148 MindsIE.ie_key(), guid)
149 query['from_timestamp'] = data['load-next']
150 if not (query['from_timestamp'] and len(entities) == self._PAGE_SIZE):
151 break
152 i += 1
153
154 def _real_extract(self, url):
155 feed_id = self._match_id(url)
156 feed = self._call_api(
157 'v1/%s/%s' % (self._FEED_PATH, feed_id),
158 feed_id, self._FEED_TYPE)[self._FEED_TYPE]
159
160 return self.playlist_result(
161 self._entries(feed['guid']), feed_id,
162 strip_or_none(feed.get('name')),
163 feed.get('briefdescription'))
164
165
166 class MindsChannelIE(MindsFeedBaseIE):
167 _FEED_TYPE = 'channel'
168 IE_NAME = 'minds:' + _FEED_TYPE
169 _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?!(?:newsfeed|media|api|archive|groups)/)(?P<id>[^/?&#]+)'
170 _FEED_PATH = 'channel'
171 _TEST = {
172 'url': 'https://www.minds.com/ottman',
173 'info_dict': {
174 'id': 'ottman',
175 'title': 'Bill Ottman',
176 'description': 'Co-creator & CEO @minds',
177 },
178 'playlist_mincount': 54,
179 }
180
181
182 class MindsGroupIE(MindsFeedBaseIE):
183 _FEED_TYPE = 'group'
184 IE_NAME = 'minds:' + _FEED_TYPE
185 _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'groups/profile/(?P<id>[0-9]+)'
186 _FEED_PATH = 'groups/group'
187 _TEST = {
188 'url': 'https://www.minds.com/groups/profile/785582576369672204/feed/videos',
189 'info_dict': {
190 'id': '785582576369672204',
191 'title': 'Cooking Videos',
192 },
193 'playlist_mincount': 1,
194 }