diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 570f8195c..14984fd6f 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -3702,6 +3702,24 @@ class InfoExtractor: (*cls.get_testcases(include_onlymatching=False), *cls.get_webpage_testcases()), (..., (('playlist', 0), None), 'info_dict', 'age_limit')) or [0]) + @classproperty(cache=True) + def _RETURN_TYPE(cls): + """What the extractor returns: "video", "playlist", "any", or None (Unknown)""" + tests = tuple(cls.get_testcases(include_onlymatching=False)) + if not tests: + return None + elif not any(k.startswith('playlist') for test in tests for k in test): + return 'video' + elif all(any(k.startswith('playlist') for k in test) for test in tests): + return 'playlist' + return 'any' + + @classmethod + def is_single_video(cls, url): + """Returns whether the URL is of a single video, None if unknown""" + assert cls.suitable(url), 'The URL must be suitable for the extractor' + return {'video': True, 'playlist': False}.get(cls._RETURN_TYPE) + @classmethod def is_suitable(cls, age_limit): """Test whether the extractor is generally suitable for the given age limit""" @@ -3953,6 +3971,7 @@ class SearchInfoExtractor(InfoExtractor): """ _MAX_RESULTS = float('inf') + _RETURN_TYPE = 'playlist' @classproperty def _VALID_URL(cls): diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index c753713c7..032972dcf 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -1050,6 +1050,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): ]*\bhref="(?Phttps://www\.youtube\.com/watch\?v=[0-9A-Za-z_-]{11})" \s[^>]*\bclass="[^"]*\blazy-load-youtube''', ] + _RETURN_TYPE = 'video' # While there are "multifeed" test cases, they don't seem to actually exist anymore _PLAYER_INFO_RE = ( r'/s/player/(?P[a-zA-Z0-9_-]{8,})/player',