aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeukhoofd <10423862+Deukhoofd@users.noreply.github.com>2024-09-14 00:50:15 +0200
committerGitHub <noreply@github.com>2024-09-13 22:50:15 +0000
commitb4760c778d0c92c6e3f2bc8346cd72c8f08595ae (patch)
tree03b094118e309e98d8eff2884d3c663959db8c70
parent9431777b4c37129a6093080c77ca59960afbb9d7 (diff)
[ie/beacon] Add extractor (#9901)
Authored by: Deukhoofd
-rw-r--r--yt_dlp/extractor/_extractors.py1
-rw-r--r--yt_dlp/extractor/beacon.py68
2 files changed, 69 insertions, 0 deletions
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index e7b162512..4302076f0 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -217,6 +217,7 @@ from .bbc import (
BBCCoUkIPlayerGroupIE,
BBCCoUkPlaylistIE,
)
+from .beacon import BeaconTvIE
from .beatbump import (
BeatBumpPlaylistIE,
BeatBumpVideoIE,
diff --git a/yt_dlp/extractor/beacon.py b/yt_dlp/extractor/beacon.py
new file mode 100644
index 000000000..ae47687cc
--- /dev/null
+++ b/yt_dlp/extractor/beacon.py
@@ -0,0 +1,68 @@
+import json
+
+from .common import InfoExtractor
+from ..utils import (
+ ExtractorError,
+ parse_iso8601,
+ traverse_obj,
+)
+
+
+class BeaconTvIE(InfoExtractor):
+ _VALID_URL = r'https?://(?:www\.)?beacon\.tv/content/(?P<id>[\w-]+)'
+
+ _TESTS = [{
+ 'url': 'https://beacon.tv/content/welcome-to-beacon',
+ 'md5': 'b3f5932d437f288e662f10f3bfc5bd04',
+ 'info_dict': {
+ 'id': 'welcome-to-beacon',
+ 'ext': 'mp4',
+ 'upload_date': '20240509',
+ 'description': 'md5:ea2bd32e71acf3f9fca6937412cc3563',
+ 'thumbnail': 'https://cdn.jwplayer.com/v2/media/I4CkkEvN/poster.jpg?width=720',
+ 'title': 'Your home for Critical Role!',
+ 'timestamp': 1715227200,
+ 'duration': 105.494,
+ },
+ }, {
+ 'url': 'https://beacon.tv/content/re-slayers-take-trailer',
+ 'md5': 'd879b091485dbed2245094c8152afd89',
+ 'info_dict': {
+ 'id': 're-slayers-take-trailer',
+ 'ext': 'mp4',
+ 'title': 'The Re-Slayer’s Take | Official Trailer',
+ 'timestamp': 1715189040,
+ 'upload_date': '20240508',
+ 'duration': 53.249,
+ 'thumbnail': 'https://cdn.jwplayer.com/v2/media/PW5ApIw3/poster.jpg?width=720',
+ },
+ }]
+
+ def _real_extract(self, url):
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
+
+ content_data = traverse_obj(self._search_nextjs_data(webpage, video_id), (
+ 'props', 'pageProps', '__APOLLO_STATE__',
+ lambda k, v: k.startswith('Content:') and v['slug'] == video_id, any))
+ if not content_data:
+ raise ExtractorError('Failed to extract content data')
+
+ jwplayer_data = traverse_obj(content_data, (
+ (('contentVideo', 'video', 'videoData'),
+ ('contentPodcast', 'podcast', 'audioData')), {json.loads}, {dict}, any))
+ if not jwplayer_data:
+ if content_data.get('contentType') not in ('videoPodcast', 'video', 'podcast'):
+ raise ExtractorError('Content is not a video/podcast', expected=True)
+ if traverse_obj(content_data, ('contentTier', '__ref')) != 'MemberTier:65b258d178f89be87b4dc0a4':
+ self.raise_login_required('This video/podcast is for members only')
+ raise ExtractorError('Failed to extract content')
+
+ return {
+ **self._parse_jwplayer_data(jwplayer_data, video_id),
+ **traverse_obj(content_data, {
+ 'title': ('title', {str}),
+ 'description': ('description', {str}),
+ 'timestamp': ('publishedAt', {parse_iso8601}),
+ }),
+ }