diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 5094920b9..aef348a44 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -80,6 +80,7 @@ from .utils import ( RejectedVideoReached, SameFileError, UnavailableVideoError, + UserNotLive, YoutubeDLCookieProcessor, YoutubeDLHandler, YoutubeDLRedirectHandler, @@ -1456,7 +1457,7 @@ class YoutubeDL: break return wrapper - def _wait_for_video(self, ie_result): + def _wait_for_video(self, ie_result={}): if (not self.params.get('wait_for_video') or ie_result.get('_type', 'video') != 'video' or ie_result.get('formats') or ie_result.get('url')): @@ -1480,7 +1481,7 @@ class YoutubeDL: if diff is None and ie_result.get('live_status') == 'is_upcoming': diff = round(random.uniform(min_wait, max_wait) if (max_wait and min_wait) else (max_wait or min_wait), 0) self.report_warning('Release time of video is not known') - elif (diff or 0) <= 0: + elif ie_result and (diff or 0) <= 0: self.report_warning('Video should already be available according to extracted info') diff = min(max(diff or 0, min_wait or 0), max_wait or float('inf')) self.to_screen(f'[wait] Waiting for {format_dur(diff)} - Press Ctrl+C to try now') @@ -1504,7 +1505,14 @@ class YoutubeDL: @_handle_extraction_exceptions def __extract_info(self, url, ie, download, extra_info, process): - ie_result = ie.extract(url) + try: + ie_result = ie.extract(url) + except UserNotLive as e: + if process: + if self.params.get('wait_for_video'): + self.report_warning(e) + self._wait_for_video() + raise if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here) self.report_warning(f'Extractor {ie.IE_NAME} returned nothing{bug_reports_message()}') return diff --git a/yt_dlp/extractor/twitch.py b/yt_dlp/extractor/twitch.py index a0cb0be02..32cfd8a08 100644 --- a/yt_dlp/extractor/twitch.py +++ b/yt_dlp/extractor/twitch.py @@ -12,10 +12,11 @@ from ..compat import ( compat_urllib_parse_urlparse, ) from ..utils import ( + ExtractorError, + UserNotLive, base_url, clean_html, dict_get, - ExtractorError, float_or_none, int_or_none, parse_duration, @@ -940,7 +941,7 @@ class TwitchStreamIE(TwitchBaseIE): stream = user['stream'] if not stream: - raise ExtractorError('%s is offline' % channel_name, expected=True) + raise UserNotLive(video_id=channel_name) access_token = self._download_access_token( channel_name, 'stream', 'channelName') diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index 09e2127e3..c60e5ca53 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -22,6 +22,7 @@ from ..jsinterp import JSInterpreter from ..utils import ( NO_DEFAULT, ExtractorError, + UserNotLive, bug_reports_message, classproperty, clean_html, @@ -5383,9 +5384,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): selected_tab_name = 'featured' requested_tab_name = mobj['tab'][1:] if 'no-youtube-channel-redirect' not in compat_opts: - if requested_tab_name == 'live': - # Live tab should have redirected to the video - raise ExtractorError('The channel is not currently live', expected=True) + if requested_tab_name == 'live': # Live tab should have redirected to the video + raise UserNotLive(video_id=mobj['id']) if requested_tab_name not in ('', selected_tab_name): redirect_warning = f'The channel does not have a {requested_tab_name} tab' if not original_tab_name: diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index f522c2102..ca39e96ac 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -1072,6 +1072,14 @@ class GeoRestrictedError(ExtractorError): self.countries = countries +class UserNotLive(ExtractorError): + """Error when a channel/user is not live""" + + def __init__(self, msg=None, **kwargs): + kwargs['expected'] = True + super().__init__(msg or 'The channel is not currently live', **kwargs) + + class DownloadError(YoutubeDLError): """Download Error exception.