4 from .common
import InfoExtractor
12 get_elements_html_by_class
,
23 class BitChuteIE(InfoExtractor
):
24 _VALID_URL
= r
'https?://(?:www\.)?bitchute\.com/(?:video|embed|torrent/[^/]+)/(?P<id>[^/?#&]+)'
25 _EMBED_REGEX
= [rf
'<(?:script|iframe)[^>]+\bsrc=(["\'])(?P<url>{_VALID_URL})']
27 'url': 'https://www.bitchute.com/video/UGlrF9o9b-Q/',
28 'md5': '7e427d7ed7af5a75b5855705ec750e2b',
32 'title': 'This is the first video on #BitChute !',
33 'description': 'md5:a0337e7b1fe39e32336974af8173a034',
34 'thumbnail': r
're:^https?://.*\.jpg$',
35 'uploader': 'BitChute',
36 'upload_date': '20170103',
39 # video not downloadable in browser, but we can recover it
40 'url': 'https://www.bitchute.com/video/2s6B3nZjAk7R/',
41 'md5': '05c12397d5354bf24494885b08d24ed1',
46 'title': 'STYXHEXENHAMMER666 - Election Fraud, Clinton 2020, EU Armies, and Gun Control',
47 'description': 'md5:228ee93bd840a24938f536aeac9cf749',
48 'thumbnail': r
're:^https?://.*\.jpg$',
49 'uploader': 'BitChute',
50 'upload_date': '20181113',
52 'params': {'check_formats': None}
,
55 'url': 'https://www.bitchute.com/video/WEnQU7XGcTdl/',
59 'title': 'Impartial Truth - Ein Letzter Appell an die Vernunft',
61 'params': {'skip_download': True}
,
62 'skip': 'Georestricted in DE',
64 'url': 'https://www.bitchute.com/embed/lbb5G1hjPhw/',
65 'only_matching': True,
67 'url': 'https://www.bitchute.com/torrent/Zee5BE49045h/szoMrox2JEI.webtorrent',
68 'only_matching': True,
73 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.57 Safari/537.36',
74 'Referer': 'https://www.bitchute.com/',
77 def _check_format(self
, video_url
, video_id
):
79 re
.sub(r
'(^https?://)(seed\d+)(?=\.bitchute\.com)', fr
'\g<1>{host}', video_url
)
80 for host
in (r
'\g<2>', 'seed122', 'seed125', 'seed126', 'seed128',
81 'seed132', 'seed150', 'seed151', 'seed152', 'seed153',
82 'seed167', 'seed171', 'seed177', 'seed305', 'seed307',
83 'seedp29xb', 'zb10-7gsop1v78'))
86 response
= self
._request
_webpage
(
87 HEADRequest(url
), video_id
=video_id
, note
=f
'Checking {url}', headers
=self
._HEADERS
)
88 except ExtractorError
as e
:
89 self
.to_screen(f
'{video_id}: URL is invalid, skipping: {e.cause}')
93 'filesize': int_or_none(response
.headers
.get('Content-Length'))
96 def _raise_if_restricted(self
, webpage
):
97 page_title
= clean_html(get_element_by_class('page-title', webpage
)) or ''
98 if re
.fullmatch(r
'(?:Channel|Video) Restricted', page_title
):
99 reason
= clean_html(get_element_by_id('page-detail', webpage
)) or page_title
100 self
.raise_geo_restricted(reason
)
102 def _real_extract(self
, url
):
103 video_id
= self
._match
_id
(url
)
104 webpage
= self
._download
_webpage
(
105 f
'https://www.bitchute.com/video/{video_id}', video_id
, headers
=self
._HEADERS
)
107 self
._raise
_if
_restricted
(webpage
)
108 publish_date
= clean_html(get_element_by_class('video-publish-date', webpage
))
109 entries
= self
._parse
_html
5_media
_entries
(url
, webpage
, video_id
)
112 for format_
in traverse_obj(entries
, (0, 'formats', ...)):
113 if self
.get_param('check_formats') is not False:
114 format_
.update(self
._check
_format
(format_
.pop('url'), video_id
) or {})
115 if 'url' not in format_
:
117 formats
.append(format_
)
120 self
.raise_no_formats(
121 'Video is unavailable. Please make sure this video is playable in the browser '
122 'before reporting this issue.', expected
=True, video_id
=video_id
)
126 'title': self
._html
_extract
_title
(webpage
) or self
._og
_search
_title
(webpage
),
127 'description': self
._og
_search
_description
(webpage
, default
=None),
128 'thumbnail': self
._og
_search
_thumbnail
(webpage
),
129 'uploader': clean_html(get_element_by_class('owner', webpage
)),
130 'upload_date': unified_strdate(self
._search
_regex
(
131 r
'at \d+:\d+ UTC on (.+?)\.', publish_date
, 'upload date', fatal
=False)),
136 class BitChuteChannelIE(InfoExtractor
):
137 _VALID_URL
= r
'https?://(?:www\.)?bitchute\.com/(?P<type>channel|playlist)/(?P<id>[^/?#&]+)'
139 'url': 'https://www.bitchute.com/channel/bitchute/',
143 'description': 'md5:5329fb3866125afa9446835594a9b138',
147 'md5': '7e427d7ed7af5a75b5855705ec750e2b',
152 'title': 'This is the first video on #BitChute !',
153 'description': 'md5:a0337e7b1fe39e32336974af8173a034',
154 'thumbnail': r
're:^https?://.*\.jpg$',
155 'uploader': 'BitChute',
156 'upload_date': '20170103',
163 'skip_download': True,
164 'playlist_items': '-1',
167 'url': 'https://www.bitchute.com/playlist/wV9Imujxasw9/',
168 'playlist_mincount': 20,
170 'id': 'wV9Imujxasw9',
171 'title': 'Bruce MacDonald and "The Light of Darkness"',
172 'description': 'md5:04913227d2714af1d36d804aa2ab6b1e',
176 _TOKEN
= 'zyG6tQcGPE5swyAEFLqKUwMuMMuF6IO2DZ6ZDQjGfsL0e4dcTLwqkTTul05Jdve7'
180 'container': 'channel-videos-container',
181 'title': 'channel-videos-title',
182 'description': 'channel-videos-text',
185 'container': 'playlist-video',
187 'description': 'description',
193 def _make_url(playlist_id
, playlist_type
):
194 return f
'https://www.bitchute.com/{playlist_type}/{playlist_id}/'
196 def _fetch_page(self
, playlist_id
, playlist_type
, page_num
):
197 playlist_url
= self
._make
_url
(playlist_id
, playlist_type
)
198 data
= self
._download
_json
(
199 f
'{playlist_url}extend/', playlist_id
, f
'Downloading page {page_num}',
200 data
=urlencode_postdata({
201 'csrfmiddlewaretoken': self
._TOKEN
,
203 'offset': page_num
* self
.PAGE_SIZE
,
205 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
206 'Referer': playlist_url
,
207 'X-Requested-With': 'XMLHttpRequest',
208 'Cookie': f
'csrftoken={self._TOKEN}',
210 if not data
.get('success'):
212 classes
= self
.HTML_CLASS_NAMES
[playlist_type
]
213 for video_html
in get_elements_html_by_class(classes
['container'], data
.get('html')):
214 video_id
= self
._search
_regex
(
215 r
'<a\s[^>]*\bhref=["\']/video
/([^
"\'/]+)', video_html, 'video id', default=None)
218 yield self.url_result(
219 f'https://www.bitchute.com/video/{video_id}', BitChuteIE, video_id, url_transparent=True,
220 title=clean_html(get_element_by_class(classes['title'], video_html)),
221 description=clean_html(get_element_by_class(classes['description'], video_html)),
222 duration=parse_duration(get_element_by_class('video-duration', video_html)),
223 view_count=parse_count(clean_html(get_element_by_class('video-views', video_html))))
225 def _real_extract(self, url):
226 playlist_type, playlist_id = self._match_valid_url(url).group('type', 'id')
227 webpage = self._download_webpage(self._make_url(playlist_id, playlist_type), playlist_id)
229 page_func = functools.partial(self._fetch_page, playlist_id, playlist_type)
230 return self.playlist_result(
231 OnDemandPagedList(page_func, self.PAGE_SIZE), playlist_id,
232 title=self._html_extract_title(webpage, default=None),
233 description=self._html_search_meta(
234 ('description', 'og:description', 'twitter:description'), webpage, default=None),
235 playlist_count=int_or_none(self._html_search_regex(
236 r'<span>(\d+)\s+videos?</span>', webpage, 'playlist count', default=None)))