mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-06-21 18:24:47 +00:00
Compare commits
4 Commits
f87cfadb5c
...
f3c255b63b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3c255b63b | ||
|
|
646904cd3a | ||
|
|
a0bda3b786 | ||
|
|
228ae9f0f2 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -153,10 +153,12 @@ jobs:
|
|||||||
'os': 'musllinux',
|
'os': 'musllinux',
|
||||||
'arch': 'x86_64',
|
'arch': 'x86_64',
|
||||||
'runner': 'ubuntu-24.04',
|
'runner': 'ubuntu-24.04',
|
||||||
|
'python_version': '3.14',
|
||||||
}, {
|
}, {
|
||||||
'os': 'musllinux',
|
'os': 'musllinux',
|
||||||
'arch': 'aarch64',
|
'arch': 'aarch64',
|
||||||
'runner': 'ubuntu-24.04-arm',
|
'runner': 'ubuntu-24.04-arm',
|
||||||
|
'python_version': '3.14',
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
INPUTS = json.loads(os.environ['INPUTS'])
|
INPUTS = json.loads(os.environ['INPUTS'])
|
||||||
|
|||||||
@ -1197,6 +1197,7 @@ from .musicdex import (
|
|||||||
MusicdexPlaylistIE,
|
MusicdexPlaylistIE,
|
||||||
MusicdexSongIE,
|
MusicdexSongIE,
|
||||||
)
|
)
|
||||||
|
from .mux import MuxIE
|
||||||
from .mx3 import (
|
from .mx3 import (
|
||||||
Mx3IE,
|
Mx3IE,
|
||||||
Mx3NeoIE,
|
Mx3NeoIE,
|
||||||
|
|||||||
@ -39,7 +39,7 @@ class BunnyCdnIE(InfoExtractor):
|
|||||||
'timestamp': 1691145748,
|
'timestamp': 1691145748,
|
||||||
'thumbnail': r're:^https?://.*\.b-cdn\.net/32e34c4b-0d72-437c-9abb-05e67657da34/thumbnail_9172dc16\.jpg',
|
'thumbnail': r're:^https?://.*\.b-cdn\.net/32e34c4b-0d72-437c-9abb-05e67657da34/thumbnail_9172dc16\.jpg',
|
||||||
'duration': 106.0,
|
'duration': 106.0,
|
||||||
'description': 'md5:981a3e899a5c78352b21ed8b2f1efd81',
|
'description': 'md5:11452bcb31f379ee3eaf1234d3264e44',
|
||||||
'upload_date': '20230804',
|
'upload_date': '20230804',
|
||||||
'title': 'Sanela ist Teil der #arbeitsmarktkraft',
|
'title': 'Sanela ist Teil der #arbeitsmarktkraft',
|
||||||
},
|
},
|
||||||
@ -58,6 +58,20 @@ class BunnyCdnIE(InfoExtractor):
|
|||||||
'thumbnail': r're:^https?://.*\.b-cdn\.net/2e8545ec-509d-4571-b855-4cf0235ccd75/thumbnail\.jpg',
|
'thumbnail': r're:^https?://.*\.b-cdn\.net/2e8545ec-509d-4571-b855-4cf0235ccd75/thumbnail\.jpg',
|
||||||
},
|
},
|
||||||
'params': {'skip_download': True},
|
'params': {'skip_download': True},
|
||||||
|
}, {
|
||||||
|
# Requires any Referer
|
||||||
|
'url': 'https://iframe.mediadelivery.net/embed/289162/6372f5a3-68df-4ef7-a115-e1110186c477',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6372f5a3-68df-4ef7-a115-e1110186c477',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '12-Creating Small Asset Blockouts -Timelapse.mp4',
|
||||||
|
'description': '',
|
||||||
|
'duration': 263.0,
|
||||||
|
'timestamp': 1724485440,
|
||||||
|
'upload_date': '20240824',
|
||||||
|
'thumbnail': r're:^https?://.*\.b-cdn\.net/6372f5a3-68df-4ef7-a115-e1110186c477/thumbnail\.jpg',
|
||||||
|
},
|
||||||
|
'params': {'skip_download': True},
|
||||||
}]
|
}]
|
||||||
_WEBPAGE_TESTS = [{
|
_WEBPAGE_TESTS = [{
|
||||||
# Stream requires Referer
|
# Stream requires Referer
|
||||||
@ -100,7 +114,7 @@ class BunnyCdnIE(InfoExtractor):
|
|||||||
video_id, library_id = self._match_valid_url(url).group('id', 'library_id')
|
video_id, library_id = self._match_valid_url(url).group('id', 'library_id')
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
f'https://iframe.mediadelivery.net/embed/{library_id}/{video_id}', video_id,
|
f'https://iframe.mediadelivery.net/embed/{library_id}/{video_id}', video_id,
|
||||||
headers=traverse_obj(smuggled_data, {'Referer': 'Referer'}),
|
headers={'Referer': smuggled_data.get('Referer') or 'https://iframe.mediadelivery.net/'},
|
||||||
query=traverse_obj(parse_qs(url), {'token': 'token', 'expires': 'expires'}))
|
query=traverse_obj(parse_qs(url), {'token': 'token', 'expires': 'expires'}))
|
||||||
|
|
||||||
if html_title := self._html_extract_title(webpage, default=None) == '403':
|
if html_title := self._html_extract_title(webpage, default=None) == '403':
|
||||||
|
|||||||
@ -1063,7 +1063,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'German Gold',
|
'title': 'German Gold',
|
||||||
'description': 'md5:f3073306553a8d9b40e6ac4cdbf09fc6',
|
'description': 'md5:f3073306553a8d9b40e6ac4cdbf09fc6',
|
||||||
'display_id': 'german-gold',
|
'display_id': 'goldrausch-in-australien/german-gold',
|
||||||
'episode': 'Episode 1',
|
'episode': 'Episode 1',
|
||||||
'episode_number': 1,
|
'episode_number': 1,
|
||||||
'season': 'Season 5',
|
'season': 'Season 5',
|
||||||
@ -1112,7 +1112,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '24 Stunden auf der Feuerwache 3',
|
'title': '24 Stunden auf der Feuerwache 3',
|
||||||
'description': 'md5:f3084ef6170bfb79f9a6e0c030e09330',
|
'description': 'md5:f3084ef6170bfb79f9a6e0c030e09330',
|
||||||
'display_id': '24-stunden-auf-der-feuerwache-3',
|
'display_id': 'feuerwache-3-alarm-in-muenchen/24-stunden-auf-der-feuerwache-3',
|
||||||
'episode': 'Episode 1',
|
'episode': 'Episode 1',
|
||||||
'episode_number': 1,
|
'episode_number': 1,
|
||||||
'season': 'Season 1',
|
'season': 'Season 1',
|
||||||
@ -1134,7 +1134,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Der Poltergeist im Kostümladen',
|
'title': 'Der Poltergeist im Kostümladen',
|
||||||
'description': 'md5:20b52b9736a0a3a7873d19a238fad7fc',
|
'description': 'md5:20b52b9736a0a3a7873d19a238fad7fc',
|
||||||
'display_id': 'der-poltergeist-im-kostumladen',
|
'display_id': 'ghost-adventures/der-poltergeist-im-kostumladen',
|
||||||
'episode': 'Episode 1',
|
'episode': 'Episode 1',
|
||||||
'episode_number': 1,
|
'episode_number': 1,
|
||||||
'season': 'Season 25',
|
'season': 'Season 25',
|
||||||
@ -1156,7 +1156,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Das Geheimnis meines Bruders',
|
'title': 'Das Geheimnis meines Bruders',
|
||||||
'description': 'md5:3167550bb582eb9c92875c86a0a20882',
|
'description': 'md5:3167550bb582eb9c92875c86a0a20882',
|
||||||
'display_id': 'das-geheimnis-meines-bruders',
|
'display_id': 'evil-gesichter-des-boesen/das-geheimnis-meines-bruders',
|
||||||
'episode': 'Episode 1',
|
'episode': 'Episode 1',
|
||||||
'episode_number': 1,
|
'episode_number': 1,
|
||||||
'season': 'Season 1',
|
'season': 'Season 1',
|
||||||
@ -1175,18 +1175,19 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
domain, programme, alternate_id = self._match_valid_url(url).groups()
|
domain, programme, alternate_id = self._match_valid_url(url).groups()
|
||||||
|
display_id = f'{programme}/{alternate_id}'
|
||||||
meta = self._download_json(
|
meta = self._download_json(
|
||||||
f'https://de-api.loma-cms.com/feloma/videos/{alternate_id}/',
|
f'https://de-api.loma-cms.com/feloma/videos/{alternate_id}/',
|
||||||
alternate_id, query={
|
display_id, query={
|
||||||
'environment': domain.split('.')[0],
|
'environment': domain.split('.')[0],
|
||||||
'v': '2',
|
'v': '2',
|
||||||
'filter[show.slug]': programme,
|
'filter[show.slug]': programme,
|
||||||
}, fatal=False)
|
}, fatal=False)
|
||||||
video_id = traverse_obj(meta, ('uid', {str}, {lambda s: s[-7:]})) or alternate_id
|
video_id = traverse_obj(meta, ('uid', {str}, {lambda s: s[-7:]})) or display_id
|
||||||
|
|
||||||
disco_api_info = self._get_disco_api_info(
|
disco_api_info = self._get_disco_api_info(
|
||||||
url, video_id, 'eu1-prod.disco-api.com', domain.replace('.', ''), 'DE')
|
url, video_id, 'eu1-prod.disco-api.com', domain.replace('.', ''), 'DE')
|
||||||
disco_api_info['display_id'] = alternate_id
|
disco_api_info['display_id'] = display_id
|
||||||
disco_api_info['categories'] = traverse_obj(meta, (
|
disco_api_info['categories'] = traverse_obj(meta, (
|
||||||
'taxonomies', lambda _, v: v['category'] == 'genre', 'title', {str.strip}, filter, all, filter))
|
'taxonomies', lambda _, v: v['category'] == 'genre', 'title', {str.strip}, filter, all, filter))
|
||||||
|
|
||||||
|
|||||||
92
yt_dlp/extractor/mux.py
Normal file
92
yt_dlp/extractor/mux.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
extract_attributes,
|
||||||
|
filter_dict,
|
||||||
|
parse_qs,
|
||||||
|
smuggle_url,
|
||||||
|
unsmuggle_url,
|
||||||
|
update_url_query,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class MuxIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:stream\.new/v|player\.mux\.com)/(?P<id>[A-Za-z0-9-]+)'
|
||||||
|
_EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//(?:stream\.new/v|player\.mux\.com)/(?P<id>[A-Za-z0-9-]+)[^"\']+)']
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://stream.new/v/OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j/embed',
|
||||||
|
'info_dict': {
|
||||||
|
'ext': 'mp4',
|
||||||
|
'id': 'OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j',
|
||||||
|
'title': 'OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://player.mux.com/OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j',
|
||||||
|
'info_dict': {
|
||||||
|
'ext': 'mp4',
|
||||||
|
'id': 'OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j',
|
||||||
|
'title': 'OCtRWZiZqKvLbnZ32WSEYiGNvHdAmB01j',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
_WEBPAGE_TESTS = [{
|
||||||
|
# iframe embed
|
||||||
|
'url': 'https://www.redbrickai.com/blog/2025-07-14-FAST-brush',
|
||||||
|
'info_dict': {
|
||||||
|
'ext': 'mp4',
|
||||||
|
'id': 'cXhzAiW1AmsHY01eRbEYFcTEAn0102aGN8sbt8JprP6Dfw',
|
||||||
|
'title': 'cXhzAiW1AmsHY01eRbEYFcTEAn0102aGN8sbt8JprP6Dfw',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# mux-player embed
|
||||||
|
'url': 'https://muxvideo.2coders.com/download/',
|
||||||
|
'info_dict': {
|
||||||
|
'ext': 'mp4',
|
||||||
|
'id': 'JBuasdg35Hw7tYmTe9k68QLPQKixL300YsWHDz5Flit8',
|
||||||
|
'title': 'JBuasdg35Hw7tYmTe9k68QLPQKixL300YsWHDz5Flit8',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# mux-player with title metadata
|
||||||
|
'url': 'https://datastar-todomvc.cross.stream/',
|
||||||
|
'info_dict': {
|
||||||
|
'ext': 'mp4',
|
||||||
|
'id': 'KX01ZSZ8CXv5SVfVwMZKJTcuBcUQmo1ReS9U5JjoHm4k',
|
||||||
|
'title': 'TodoMVC with Datastar Tutorial',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_embed_urls(cls, url, webpage):
|
||||||
|
yield from super()._extract_embed_urls(url, webpage)
|
||||||
|
for mux_player in re.findall(r'<mux-(?:player|video)\b[^>]*\bplayback-id=[^>]+>', webpage):
|
||||||
|
attrs = extract_attributes(mux_player)
|
||||||
|
playback_id = attrs.get('playback-id')
|
||||||
|
if not playback_id:
|
||||||
|
continue
|
||||||
|
token = attrs.get('playback-token') or traverse_obj(playback_id, ({parse_qs}, 'token', -1))
|
||||||
|
playback_id = playback_id.partition('?')[0]
|
||||||
|
|
||||||
|
embed_url = update_url_query(
|
||||||
|
f'https://player.mux.com/{playback_id}',
|
||||||
|
filter_dict({'playback-token': token}))
|
||||||
|
if title := attrs.get('metadata-video-title'):
|
||||||
|
embed_url = smuggle_url(embed_url, {'title': title})
|
||||||
|
yield embed_url
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
token = traverse_obj(parse_qs(url), ('playback-token', -1))
|
||||||
|
|
||||||
|
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
||||||
|
f'https://stream.mux.com/{video_id}.m3u8', video_id, 'mp4',
|
||||||
|
query=filter_dict({'token': token}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': smuggled_data.get('title') or video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user