diff --git a/yt_dlp/downloader/__init__.py b/yt_dlp/downloader/__init__.py index 17458b9b94..3127126f39 100644 --- a/yt_dlp/downloader/__init__.py +++ b/yt_dlp/downloader/__init__.py @@ -36,6 +36,7 @@ def get_suitable_downloader(info_dict, params={}, default=NO_DEFAULT, protocol=N from .websocket import WebSocketFragmentFD from .youtube_live_chat import YoutubeLiveChatFD from .bunnycdn import BunnyCdnFD +from .twitter import TwitterSpacesFD PROTOCOL_MAP = { 'rtmp': RtmpFD, @@ -56,6 +57,7 @@ def get_suitable_downloader(info_dict, params={}, default=NO_DEFAULT, protocol=N 'youtube_live_chat': YoutubeLiveChatFD, 'youtube_live_chat_replay': YoutubeLiveChatFD, 'bunnycdn': BunnyCdnFD, + 'twitter_spaces': TwitterSpacesFD, } diff --git a/yt_dlp/downloader/twitter.py b/yt_dlp/downloader/twitter.py new file mode 100644 index 0000000000..fa706e3140 --- /dev/null +++ b/yt_dlp/downloader/twitter.py @@ -0,0 +1,15 @@ +from .hls import HlsFD + + +class TwitterSpacesFD(HlsFD): + FD_NAME = 'twitterspaces' + + def _read_fragment(self, ctx): + frag_content = super()._read_fragment(ctx) + idx = frag_content.find(bytes( + [0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x50, 0x52, 0x49, 0x56])) + if idx > 0: + # self.to_screen(f'[{self.FD_NAME}] Removing partial header ({idx} bytes)') + return frag_content[idx:] + else: + return frag_content diff --git a/yt_dlp/extractor/twitter.py b/yt_dlp/extractor/twitter.py index 65182b971b..a20ece6f19 100644 --- a/yt_dlp/extractor/twitter.py +++ b/yt_dlp/extractor/twitter.py @@ -1852,9 +1852,9 @@ def _real_extract(self, url): ('source', ('noRedirectPlaybackUrl', 'location'), {url_or_none}), get_all=False) is_audio_space = source and 'audio-space' in source formats = self._extract_m3u8_formats( - source, metadata['media_key'], 'm4a' if is_audio_space else 'mp4', - # XXX: Some audio-only Spaces need ffmpeg as downloader - entry_protocol='m3u8' if is_audio_space else 'm3u8_native', + source, metadata['media_key'], 'aac' if is_audio_space else 'mp4', + # XXX: Some audio-only Spaces need to truncate heads of their fragments, not only ffmpeg as downloader + entry_protocol='twitter_spaces' if is_audio_space else 'm3u8_native', live=is_live, headers=headers, fatal=False) if source else [] if is_audio_space: for fmt in formats: