mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-11-04 08:35:12 +00:00 
			
		
		
		
	[ie/mtv] Overhaul extractors (#14052)
Adds SouthParkComBrIE and SouthParkCoUkIE Removes these extractors: - CMTIE: migrated to Paramount+ - ComedyCentralTVIE: migrated to Paramount+ - MTVDEIE: migrated to Paramount+ - MTVItaliaIE: migrated to Paramount+ - MTVItaliaProgrammaIE: migrated to Paramount+ - MTVJapanIE: migrated to JP Services - MTVServicesEmbeddedIE: dead domain - MTVVideoIE: migrated to Paramount+ - NickBrIE: redirects to landing page w/o any videos - NickDeIE: redirects to landing page w/o any videos - NickRuIE: redirects to landing page w/o any videos - BellatorIE: migrated to PFL - ParamountNetworkIE: migrated to Paramount+ - SouthParkNlIE: site no longer exists - TVLandIE: migrated to Paramount+ Closes #169, Closes #1711, Closes #1712, Closes #2621, Closes #3167, Closes #3893, Closes #4552, Closes #4702, Closes #4928, Closes #5249, Closes #6156, Closes #8722, Closes #9896, Closes #10168, Closes #12765, Closes #13446, Closes #14009 Authored by: bashonly, doe1080, Randalix, seproDev Co-authored-by: doe1080 <98906116+doe1080@users.noreply.github.com> Co-authored-by: Randalix <23729538+Randalix@users.noreply.github.com> Co-authored-by: sepro <sepro@sepr0.com>
This commit is contained in:
		@@ -14,7 +14,6 @@ from yt_dlp.extractor import (
 | 
				
			|||||||
    NRKTVIE,
 | 
					    NRKTVIE,
 | 
				
			||||||
    PBSIE,
 | 
					    PBSIE,
 | 
				
			||||||
    CeskaTelevizeIE,
 | 
					    CeskaTelevizeIE,
 | 
				
			||||||
    ComedyCentralIE,
 | 
					 | 
				
			||||||
    DailymotionIE,
 | 
					    DailymotionIE,
 | 
				
			||||||
    DemocracynowIE,
 | 
					    DemocracynowIE,
 | 
				
			||||||
    LyndaIE,
 | 
					    LyndaIE,
 | 
				
			||||||
@@ -279,23 +278,6 @@ class TestNPOSubtitles(BaseTestSubtitles):
 | 
				
			|||||||
        self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4')
 | 
					        self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@is_download_test
 | 
					 | 
				
			||||||
@unittest.skip('IE broken')
 | 
					 | 
				
			||||||
class TestMTVSubtitles(BaseTestSubtitles):
 | 
					 | 
				
			||||||
    url = 'http://www.cc.com/video-clips/p63lk0/adam-devine-s-house-party-chasing-white-swans'
 | 
					 | 
				
			||||||
    IE = ComedyCentralIE
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def getInfoDict(self):
 | 
					 | 
				
			||||||
        return super().getInfoDict()['entries'][0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_allsubtitles(self):
 | 
					 | 
				
			||||||
        self.DL.params['writesubtitles'] = True
 | 
					 | 
				
			||||||
        self.DL.params['allsubtitles'] = True
 | 
					 | 
				
			||||||
        subtitles = self.getSubtitles()
 | 
					 | 
				
			||||||
        self.assertEqual(set(subtitles.keys()), {'en'})
 | 
					 | 
				
			||||||
        self.assertEqual(md5(subtitles['en']), '78206b8d8a0cfa9da64dc026eea48961')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@is_download_test
 | 
					@is_download_test
 | 
				
			||||||
class TestNRKSubtitles(BaseTestSubtitles):
 | 
					class TestNRKSubtitles(BaseTestSubtitles):
 | 
				
			||||||
    url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1'
 | 
					    url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -398,16 +398,12 @@ from .cloudflarestream import CloudflareStreamIE
 | 
				
			|||||||
from .cloudycdn import CloudyCDNIE
 | 
					from .cloudycdn import CloudyCDNIE
 | 
				
			||||||
from .clubic import ClubicIE
 | 
					from .clubic import ClubicIE
 | 
				
			||||||
from .clyp import ClypIE
 | 
					from .clyp import ClypIE
 | 
				
			||||||
from .cmt import CMTIE
 | 
					 | 
				
			||||||
from .cnbc import CNBCVideoIE
 | 
					from .cnbc import CNBCVideoIE
 | 
				
			||||||
from .cnn import (
 | 
					from .cnn import (
 | 
				
			||||||
    CNNIE,
 | 
					    CNNIE,
 | 
				
			||||||
    CNNIndonesiaIE,
 | 
					    CNNIndonesiaIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .comedycentral import (
 | 
					from .comedycentral import ComedyCentralIE
 | 
				
			||||||
    ComedyCentralIE,
 | 
					 | 
				
			||||||
    ComedyCentralTVIE,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .commonmistakes import (
 | 
					from .commonmistakes import (
 | 
				
			||||||
    BlobIE,
 | 
					    BlobIE,
 | 
				
			||||||
    CommonMistakesIE,
 | 
					    CommonMistakesIE,
 | 
				
			||||||
@@ -1180,15 +1176,7 @@ from .moview import MoviewPlayIE
 | 
				
			|||||||
from .moviezine import MoviezineIE
 | 
					from .moviezine import MoviezineIE
 | 
				
			||||||
from .movingimage import MovingImageIE
 | 
					from .movingimage import MovingImageIE
 | 
				
			||||||
from .msn import MSNIE
 | 
					from .msn import MSNIE
 | 
				
			||||||
from .mtv import (
 | 
					from .mtv import MTVIE
 | 
				
			||||||
    MTVDEIE,
 | 
					 | 
				
			||||||
    MTVIE,
 | 
					 | 
				
			||||||
    MTVItaliaIE,
 | 
					 | 
				
			||||||
    MTVItaliaProgrammaIE,
 | 
					 | 
				
			||||||
    MTVJapanIE,
 | 
					 | 
				
			||||||
    MTVServicesEmbeddedIE,
 | 
					 | 
				
			||||||
    MTVVideoIE,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .muenchentv import MuenchenTVIE
 | 
					from .muenchentv import MuenchenTVIE
 | 
				
			||||||
from .murrtube import (
 | 
					from .murrtube import (
 | 
				
			||||||
    MurrtubeIE,
 | 
					    MurrtubeIE,
 | 
				
			||||||
@@ -1330,12 +1318,7 @@ from .nhk import (
 | 
				
			|||||||
    NhkVodProgramIE,
 | 
					    NhkVodProgramIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .nhl import NHLIE
 | 
					from .nhl import NHLIE
 | 
				
			||||||
from .nick import (
 | 
					from .nick import NickIE
 | 
				
			||||||
    NickBrIE,
 | 
					 | 
				
			||||||
    NickDeIE,
 | 
					 | 
				
			||||||
    NickIE,
 | 
					 | 
				
			||||||
    NickRuIE,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .niconico import (
 | 
					from .niconico import (
 | 
				
			||||||
    NiconicoHistoryIE,
 | 
					    NiconicoHistoryIE,
 | 
				
			||||||
    NiconicoIE,
 | 
					    NiconicoIE,
 | 
				
			||||||
@@ -1923,12 +1906,13 @@ from .soundgasm import (
 | 
				
			|||||||
    SoundgasmProfileIE,
 | 
					    SoundgasmProfileIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .southpark import (
 | 
					from .southpark import (
 | 
				
			||||||
 | 
					    SouthParkComBrIE,
 | 
				
			||||||
 | 
					    SouthParkCoUkIE,
 | 
				
			||||||
    SouthParkDeIE,
 | 
					    SouthParkDeIE,
 | 
				
			||||||
    SouthParkDkIE,
 | 
					    SouthParkDkIE,
 | 
				
			||||||
    SouthParkEsIE,
 | 
					    SouthParkEsIE,
 | 
				
			||||||
    SouthParkIE,
 | 
					    SouthParkIE,
 | 
				
			||||||
    SouthParkLatIE,
 | 
					    SouthParkLatIE,
 | 
				
			||||||
    SouthParkNlIE,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .sovietscloset import (
 | 
					from .sovietscloset import (
 | 
				
			||||||
    SovietsClosetIE,
 | 
					    SovietsClosetIE,
 | 
				
			||||||
@@ -1939,10 +1923,6 @@ from .spankbang import (
 | 
				
			|||||||
    SpankBangPlaylistIE,
 | 
					    SpankBangPlaylistIE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .spiegel import SpiegelIE
 | 
					from .spiegel import SpiegelIE
 | 
				
			||||||
from .spike import (
 | 
					 | 
				
			||||||
    BellatorIE,
 | 
					 | 
				
			||||||
    ParamountNetworkIE,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .sport5 import Sport5IE
 | 
					from .sport5 import Sport5IE
 | 
				
			||||||
from .sportbox import SportBoxIE
 | 
					from .sportbox import SportBoxIE
 | 
				
			||||||
from .sportdeutschland import SportDeutschlandIE
 | 
					from .sportdeutschland import SportDeutschlandIE
 | 
				
			||||||
@@ -2207,7 +2187,6 @@ from .tvc import (
 | 
				
			|||||||
from .tver import TVerIE
 | 
					from .tver import TVerIE
 | 
				
			||||||
from .tvigle import TvigleIE
 | 
					from .tvigle import TvigleIE
 | 
				
			||||||
from .tviplayer import TVIPlayerIE
 | 
					from .tviplayer import TVIPlayerIE
 | 
				
			||||||
from .tvland import TVLandIE
 | 
					 | 
				
			||||||
from .tvn24 import TVN24IE
 | 
					from .tvn24 import TVN24IE
 | 
				
			||||||
from .tvnoe import TVNoeIE
 | 
					from .tvnoe import TVNoeIE
 | 
				
			||||||
from .tvopengr import (
 | 
					from .tvopengr import (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,79 +1,47 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					from .mtv import MTVServicesBaseIE
 | 
				
			||||||
from ..utils import unified_strdate
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BetIE(MTVServicesInfoExtractor):
 | 
					class BetIE(MTVServicesBaseIE):
 | 
				
			||||||
    _WORKING = False
 | 
					    _VALID_URL = r'https?://(?:www\.)?bet\.com/(?:video-clips|episodes)/(?P<id>[\da-z]{6})'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?bet\.com/(?:[^/]+/)+(?P<id>.+?)\.html'
 | 
					    _TESTS = [{
 | 
				
			||||||
    _TESTS = [
 | 
					        'url': 'https://www.bet.com/video-clips/w9mk7v',
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            'url': 'http://www.bet.com/news/politics/2014/12/08/in-bet-exclusive-obama-talks-race-and-racism.html',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
                'id': '07e96bd3-8850-3051-b856-271b457f0ab8',
 | 
					            'id': '3022d121-d191-43fd-b5fb-b2c26f335497',
 | 
				
			||||||
                'display_id': 'in-bet-exclusive-obama-talks-race-and-racism',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
                'ext': 'flv',
 | 
					            'display_id': 'w9mk7v',
 | 
				
			||||||
                'title': 'A Conversation With President Obama',
 | 
					            'title': 'New Normal',
 | 
				
			||||||
                'description': 'President Obama urges persistence in confronting racism and bias.',
 | 
					            'description': 'md5:d7898c124713b4646cecad9d16ff01f3',
 | 
				
			||||||
                'duration': 1534,
 | 
					            'duration': 30.08,
 | 
				
			||||||
                'upload_date': '20141208',
 | 
					            'series': 'Tyler Perry\'s Sistas',
 | 
				
			||||||
                'thumbnail': r're:(?i)^https?://.*\.jpg$',
 | 
					            'season': 'Season 0',
 | 
				
			||||||
                'subtitles': {
 | 
					            'season_number': 0,
 | 
				
			||||||
                    'en': 'mincount:2',
 | 
					            'episode': 'Episode 0',
 | 
				
			||||||
 | 
					            'episode_number': 0,
 | 
				
			||||||
 | 
					            'timestamp': 1755269073,
 | 
				
			||||||
 | 
					            'upload_date': '20250815',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
            },
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
            'params': {
 | 
					    }, {
 | 
				
			||||||
                # rtmp download
 | 
					        'url': 'https://www.bet.com/episodes/nmce72/tyler-perry-s-sistas-heavy-is-the-crown-season-9-ep-5',
 | 
				
			||||||
                'skip_download': True,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            'url': 'http://www.bet.com/video/news/national/2014/justice-for-ferguson-a-community-reacts.html',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
                'id': '9f516bf1-7543-39c4-8076-dd441b459ba9',
 | 
					            'id': '6427562b-3029-11f0-b405-16fff45bc035',
 | 
				
			||||||
                'display_id': 'justice-for-ferguson-a-community-reacts',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
                'ext': 'flv',
 | 
					            'display_id': 'nmce72',
 | 
				
			||||||
                'title': 'Justice for Ferguson: A Community Reacts',
 | 
					            'title': 'Heavy Is the Crown',
 | 
				
			||||||
                'description': 'A BET News special.',
 | 
					            'description': 'md5:1ed345d3157a50572d2464afcc7a652a',
 | 
				
			||||||
                'duration': 1696,
 | 
					            'channel': 'BET',
 | 
				
			||||||
                'upload_date': '20141125',
 | 
					            'duration': 2550.0,
 | 
				
			||||||
                'thumbnail': r're:(?i)^https?://.*\.jpg$',
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref',
 | 
				
			||||||
                'subtitles': {
 | 
					            'series': 'Tyler Perry\'s Sistas',
 | 
				
			||||||
                    'en': 'mincount:2',
 | 
					            'season': 'Season 9',
 | 
				
			||||||
 | 
					            'season_number': 9,
 | 
				
			||||||
 | 
					            'episode': 'Episode 5',
 | 
				
			||||||
 | 
					            'episode_number': 5,
 | 
				
			||||||
 | 
					            'timestamp': 1755165600,
 | 
				
			||||||
 | 
					            'upload_date': '20250814',
 | 
				
			||||||
 | 
					            'release_timestamp': 1755129600,
 | 
				
			||||||
 | 
					            'release_date': '20250814',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
            },
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
            'params': {
 | 
					        'skip': 'Requires provider sign-in',
 | 
				
			||||||
                # rtmp download
 | 
					    }]
 | 
				
			||||||
                'skip_download': True,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/bet-mrss-player'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'uuid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_mgid(self, webpage):
 | 
					 | 
				
			||||||
        return self._search_regex(r'data-uri="([^"]+)', webpage, 'mgid')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        display_id = self._match_id(url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, display_id)
 | 
					 | 
				
			||||||
        mgid = self._extract_mgid(webpage)
 | 
					 | 
				
			||||||
        videos_info = self._get_videos_info(mgid)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        info_dict = videos_info['entries'][0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        upload_date = unified_strdate(self._html_search_meta('date', webpage))
 | 
					 | 
				
			||||||
        description = self._html_search_meta('description', webpage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        info_dict.update({
 | 
					 | 
				
			||||||
            'display_id': display_id,
 | 
					 | 
				
			||||||
            'description': description,
 | 
					 | 
				
			||||||
            'upload_date': upload_date,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return info_dict
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
from .mtv import MTVIE
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# TODO: Remove - Reason: Outdated Site
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CMTIE(MTVIE):  # XXX: Do not subclass from concrete IE
 | 
					 | 
				
			||||||
    _WORKING = False
 | 
					 | 
				
			||||||
    IE_NAME = 'cmt.com'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?cmt\.com/(?:videos|shows|(?:full-)?episodes|video-clips)/(?P<id>[^/]+)'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.cmt.com/videos/garth-brooks/989124/the-call-featuring-trisha-yearwood.jhtml#artist=30061',
 | 
					 | 
				
			||||||
        'md5': 'e6b7ef3c4c45bbfae88061799bbba6c2',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '989124',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Garth Brooks - "The Call (featuring Trisha Yearwood)"',
 | 
					 | 
				
			||||||
            'description': 'Blame It All On My Roots',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'skip': 'Video not available',
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.cmt.com/videos/misc/1504699/still-the-king-ep-109-in-3-minutes.jhtml#id=1739908',
 | 
					 | 
				
			||||||
        'md5': 'e61a801ca4a183a466c08bd98dccbb1c',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '1504699',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Still The King Ep. 109 in 3 Minutes',
 | 
					 | 
				
			||||||
            'description': 'Relive or catch up with Still The King by watching this recap of season 1, episode 9.',
 | 
					 | 
				
			||||||
            'timestamp': 1469421000.0,
 | 
					 | 
				
			||||||
            'upload_date': '20160725',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.cmt.com/shows/party-down-south/party-down-south-ep-407-gone-girl/1738172/playlist/#id=1738172',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.cmt.com/full-episodes/537qb3/nashville-the-wayfaring-stranger-season-5-ep-501',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.cmt.com/video-clips/t9e4ci/nashville-juliette-in-2-minutes',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_mgid(self, webpage, url):
 | 
					 | 
				
			||||||
        mgid = self._search_regex(
 | 
					 | 
				
			||||||
            r'MTVN\.VIDEO\.contentUri\s*=\s*([\'"])(?P<mgid>.+?)\1',
 | 
					 | 
				
			||||||
            webpage, 'mgid', group='mgid', default=None)
 | 
					 | 
				
			||||||
        if not mgid:
 | 
					 | 
				
			||||||
            mgid = self._extract_triforce_mgid(webpage)
 | 
					 | 
				
			||||||
        return mgid
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					 | 
				
			||||||
        mgid = self._extract_mgid(webpage, url)
 | 
					 | 
				
			||||||
        return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}')
 | 
					 | 
				
			||||||
@@ -1,55 +1,27 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					from .mtv import MTVServicesBaseIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ComedyCentralIE(MTVServicesInfoExtractor):
 | 
					class ComedyCentralIE(MTVServicesBaseIE):
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?cc\.com/(?:episodes|video(?:-clips)?|collection-playlist|movies)/(?P<id>[0-9a-z]{6})'
 | 
					    _VALID_URL = r'https?://(?:www\.)?cc\.com/video-clips/(?P<id>[\da-z]{6})'
 | 
				
			||||||
    _FEED_URL = 'http://comedycentral.com/feeds/mrss/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.cc.com/video-clips/5ke9v2/the-daily-show-with-trevor-noah-doc-rivers-and-steve-ballmer---the-nba-player-strike',
 | 
					        'url': 'https://www.cc.com/video-clips/wl12cx',
 | 
				
			||||||
        'md5': 'b8acb347177c680ff18a292aa2166f80',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '89ccc86e-1b02-4f83-b0c9-1d9592ecd025',
 | 
					            'id': 'dec6953e-80c8-43b3-96cd-05e9230e704d',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'The Daily Show with Trevor Noah|August 28, 2020|25|25149|Doc Rivers and Steve Ballmer - The NBA Player Strike',
 | 
					            'display_id': 'wl12cx',
 | 
				
			||||||
            'description': 'md5:5334307c433892b85f4f5e5ac9ef7498',
 | 
					            'title': 'Alison Brie and Dave Franco -"Together"- Extended Interview',
 | 
				
			||||||
            'timestamp': 1598670000,
 | 
					            'description': 'md5:ec68e38d3282f863de9cde0ce5cd231c',
 | 
				
			||||||
            'upload_date': '20200829',
 | 
					            'duration': 516.76,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'The Daily Show',
 | 
				
			||||||
 | 
					            'season': 'Season 30',
 | 
				
			||||||
 | 
					            'season_number': 30,
 | 
				
			||||||
 | 
					            'episode': 'Episode 0',
 | 
				
			||||||
 | 
					            'episode_number': 0,
 | 
				
			||||||
 | 
					            'timestamp': 1753973314,
 | 
				
			||||||
 | 
					            'upload_date': '20250731',
 | 
				
			||||||
 | 
					            'release_timestamp': 1753977914,
 | 
				
			||||||
 | 
					            'release_date': '20250731',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }, {
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
        'url': 'http://www.cc.com/episodes/pnzzci/drawn-together--american-idol--parody-clip-show-season-3-ep-314',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'https://www.cc.com/video/k3sdvm/the-daily-show-with-jon-stewart-exclusive-the-fourth-estate',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'https://www.cc.com/collection-playlist/cosnej/stand-up-specials/t6vtjb',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'https://www.cc.com/movies/tkp406/a-cluesterfuenke-christmas',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ComedyCentralTVIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?comedycentral\.tv/folgen/(?P<id>[0-9a-z]{6})'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'https://www.comedycentral.tv/folgen/pxdpec/josh-investigates-klimawandel-staffel-1-ep-1',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '15907dc3-ec3c-11e8-a442-0e40cf2fc285',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Josh Investigates',
 | 
					 | 
				
			||||||
            'description': 'Steht uns das Ende der Welt bevor?',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['DE']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'accountOverride': 'intl.mtvi.com',
 | 
					 | 
				
			||||||
            'arcEp': 'web.cc.tv',
 | 
					 | 
				
			||||||
            'ep': 'b9032c3a',
 | 
					 | 
				
			||||||
            'imageEp': 'web.cc.tv',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,652 +1,268 @@
 | 
				
			|||||||
import re
 | 
					import base64
 | 
				
			||||||
import xml.etree.ElementTree
 | 
					import json
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import urllib.parse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..networking import HEADRequest, Request
 | 
					from ..networking.exceptions import HTTPError
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    ExtractorError,
 | 
					    ExtractorError,
 | 
				
			||||||
    RegexNotFoundError,
 | 
					 | 
				
			||||||
    find_xpath_attr,
 | 
					 | 
				
			||||||
    fix_xml_ampersands,
 | 
					 | 
				
			||||||
    float_or_none,
 | 
					    float_or_none,
 | 
				
			||||||
    int_or_none,
 | 
					    int_or_none,
 | 
				
			||||||
    join_nonempty,
 | 
					    js_to_json,
 | 
				
			||||||
    strip_or_none,
 | 
					    jwt_decode_hs256,
 | 
				
			||||||
    timeconvert,
 | 
					    parse_iso8601,
 | 
				
			||||||
    try_get,
 | 
					    parse_qs,
 | 
				
			||||||
    unescapeHTML,
 | 
					    update_url,
 | 
				
			||||||
    update_url_query,
 | 
					    update_url_query,
 | 
				
			||||||
    url_basename,
 | 
					    url_or_none,
 | 
				
			||||||
    xpath_text,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from ..utils.traversal import require, traverse_obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _media_xml_tag(tag):
 | 
					class MTVServicesBaseIE(InfoExtractor):
 | 
				
			||||||
    return f'{{http://search.yahoo.com/mrss/}}{tag}'
 | 
					    _GEO_BYPASS = False
 | 
				
			||||||
 | 
					    _GEO_COUNTRIES = ['US']
 | 
				
			||||||
 | 
					    _CACHE_SECTION = 'mtvservices'
 | 
				
			||||||
class MTVServicesInfoExtractor(InfoExtractor):
 | 
					    _ACCESS_TOKEN_KEY = 'access'
 | 
				
			||||||
    _MOBILE_TEMPLATE = None
 | 
					    _REFRESH_TOKEN_KEY = 'refresh'
 | 
				
			||||||
    _LANG = None
 | 
					    _MEDIA_TOKEN_KEY = 'media'
 | 
				
			||||||
 | 
					    _token_cache = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def _id_from_uri(uri):
 | 
					    def _jwt_is_expired(token):
 | 
				
			||||||
        return uri.split(':')[-1]
 | 
					        return jwt_decode_hs256(token)['exp'] - time.time() < 120
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def _remove_template_parameter(url):
 | 
					    def _get_auth_suite_data(config):
 | 
				
			||||||
        # Remove the templates, like &device={device}
 | 
					        return traverse_obj(config, {
 | 
				
			||||||
        return re.sub(r'&[^=]*?={.*?}(?=(&|$))', '', url)
 | 
					            'clientId': ('clientId', {str}),
 | 
				
			||||||
 | 
					            'countryCode': ('countryCode', {str}),
 | 
				
			||||||
    def _get_feed_url(self, uri, url=None):
 | 
					 | 
				
			||||||
        return self._FEED_URL
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_thumbnail_url(self, uri, itemdoc):
 | 
					 | 
				
			||||||
        search_path = '{}/{}'.format(_media_xml_tag('group'), _media_xml_tag('thumbnail'))
 | 
					 | 
				
			||||||
        thumb_node = itemdoc.find(search_path)
 | 
					 | 
				
			||||||
        if thumb_node is None:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
        return thumb_node.get('url') or thumb_node.text or None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_mobile_video_formats(self, mtvn_id):
 | 
					 | 
				
			||||||
        webpage_url = self._MOBILE_TEMPLATE % mtvn_id
 | 
					 | 
				
			||||||
        req = Request(webpage_url)
 | 
					 | 
				
			||||||
        # Otherwise we get a webpage that would execute some javascript
 | 
					 | 
				
			||||||
        req.headers['User-Agent'] = 'curl/7'
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(req, mtvn_id,
 | 
					 | 
				
			||||||
                                         'Downloading mobile page')
 | 
					 | 
				
			||||||
        metrics_url = unescapeHTML(self._search_regex(r'<a href="(http://metrics.+?)"', webpage, 'url'))
 | 
					 | 
				
			||||||
        req = HEADRequest(metrics_url)
 | 
					 | 
				
			||||||
        response = self._request_webpage(req, mtvn_id, 'Resolving url')
 | 
					 | 
				
			||||||
        url = response.url
 | 
					 | 
				
			||||||
        # Transform the url to get the best quality:
 | 
					 | 
				
			||||||
        url = re.sub(r'.+pxE=mp4', 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=0+_pxK=18639+_pxE=mp4', url, count=1)
 | 
					 | 
				
			||||||
        return [{'url': url, 'ext': 'mp4'}]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_video_formats(self, mdoc, mtvn_id, video_id):
 | 
					 | 
				
			||||||
        if re.match(r'.*/(error_country_block\.swf|geoblock\.mp4|copyright_error\.flv(?:\?geo\b.+?)?)$', mdoc.find('.//src').text) is not None:
 | 
					 | 
				
			||||||
            if mtvn_id is not None and self._MOBILE_TEMPLATE is not None:
 | 
					 | 
				
			||||||
                self.to_screen('The normal version is not available from your '
 | 
					 | 
				
			||||||
                               'country, trying with the mobile version')
 | 
					 | 
				
			||||||
                return self._extract_mobile_video_formats(mtvn_id)
 | 
					 | 
				
			||||||
            raise ExtractorError('This video is not available from your country.',
 | 
					 | 
				
			||||||
                                 expected=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        formats = []
 | 
					 | 
				
			||||||
        for rendition in mdoc.findall('.//rendition'):
 | 
					 | 
				
			||||||
            if rendition.get('method') == 'hls':
 | 
					 | 
				
			||||||
                hls_url = rendition.find('./src').text
 | 
					 | 
				
			||||||
                formats.extend(self._extract_m3u8_formats(
 | 
					 | 
				
			||||||
                    hls_url, video_id, ext='mp4', entry_protocol='m3u8_native',
 | 
					 | 
				
			||||||
                    m3u8_id='hls', fatal=False))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                # fms
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    _, _, ext = rendition.attrib['type'].partition('/')
 | 
					 | 
				
			||||||
                    rtmp_video_url = rendition.find('./src').text
 | 
					 | 
				
			||||||
                    if 'error_not_available.swf' in rtmp_video_url:
 | 
					 | 
				
			||||||
                        raise ExtractorError(
 | 
					 | 
				
			||||||
                            f'{self.IE_NAME} said: video is not available',
 | 
					 | 
				
			||||||
                            expected=True)
 | 
					 | 
				
			||||||
                    if rtmp_video_url.endswith('siteunavail.png'):
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    formats.extend([{
 | 
					 | 
				
			||||||
                        'ext': 'flv' if rtmp_video_url.startswith('rtmp') else ext,
 | 
					 | 
				
			||||||
                        'url': rtmp_video_url,
 | 
					 | 
				
			||||||
                        'format_id': join_nonempty(
 | 
					 | 
				
			||||||
                            'rtmp' if rtmp_video_url.startswith('rtmp') else None,
 | 
					 | 
				
			||||||
                            rendition.get('bitrate')),
 | 
					 | 
				
			||||||
                        'width': int(rendition.get('width')),
 | 
					 | 
				
			||||||
                        'height': int(rendition.get('height')),
 | 
					 | 
				
			||||||
                    }])
 | 
					 | 
				
			||||||
                except (KeyError, TypeError):
 | 
					 | 
				
			||||||
                    raise ExtractorError('Invalid rendition field.')
 | 
					 | 
				
			||||||
        return formats
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_subtitles(self, mdoc, mtvn_id):
 | 
					 | 
				
			||||||
        subtitles = {}
 | 
					 | 
				
			||||||
        for transcript in mdoc.findall('.//transcript'):
 | 
					 | 
				
			||||||
            if transcript.get('kind') != 'captions':
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            lang = transcript.get('srclang')
 | 
					 | 
				
			||||||
            for typographic in transcript.findall('./typographic'):
 | 
					 | 
				
			||||||
                sub_src = typographic.get('src')
 | 
					 | 
				
			||||||
                if not sub_src:
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                ext = typographic.get('format')
 | 
					 | 
				
			||||||
                if ext == 'cea-608':
 | 
					 | 
				
			||||||
                    ext = 'scc'
 | 
					 | 
				
			||||||
                subtitles.setdefault(lang, []).append({
 | 
					 | 
				
			||||||
                    'url': str(sub_src),
 | 
					 | 
				
			||||||
                    'ext': ext,
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        return subtitles
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_video_info(self, itemdoc, use_hls=True):
 | 
					    def _call_auth_api(self, path, config, display_id=None, note=None, data=None, headers=None, query=None):
 | 
				
			||||||
        uri = itemdoc.find('guid').text
 | 
					        headers = {
 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					            'Accept': 'application/json',
 | 
				
			||||||
        self.report_extraction(video_id)
 | 
					            'Client-Description': 'deviceName=Chrome Windows;deviceType=desktop;system=Windows NT 10.0',
 | 
				
			||||||
        content_el = itemdoc.find('{}/{}'.format(_media_xml_tag('group'), _media_xml_tag('content')))
 | 
					            'Api-Version': '2025-07-09',
 | 
				
			||||||
        mediagen_url = self._remove_template_parameter(content_el.attrib['url'])
 | 
					            **(headers or {}),
 | 
				
			||||||
        mediagen_url = mediagen_url.replace('device={device}', '')
 | 
					 | 
				
			||||||
        if 'acceptMethods' not in mediagen_url:
 | 
					 | 
				
			||||||
            mediagen_url += '&' if '?' in mediagen_url else '?'
 | 
					 | 
				
			||||||
            mediagen_url += 'acceptMethods='
 | 
					 | 
				
			||||||
            mediagen_url += 'hls' if use_hls else 'fms'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        mediagen_doc = self._download_xml(
 | 
					 | 
				
			||||||
            mediagen_url, video_id, 'Downloading video urls', fatal=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not isinstance(mediagen_doc, xml.etree.ElementTree.Element):
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        item = mediagen_doc.find('./video/item')
 | 
					 | 
				
			||||||
        if item is not None and item.get('type') == 'text':
 | 
					 | 
				
			||||||
            message = f'{self.IE_NAME} returned error: '
 | 
					 | 
				
			||||||
            if item.get('code') is not None:
 | 
					 | 
				
			||||||
                message += '{} - '.format(item.get('code'))
 | 
					 | 
				
			||||||
            message += item.text
 | 
					 | 
				
			||||||
            raise ExtractorError(message, expected=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        description = strip_or_none(xpath_text(itemdoc, 'description'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        timestamp = timeconvert(xpath_text(itemdoc, 'pubDate'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        title_el = None
 | 
					 | 
				
			||||||
        if title_el is None:
 | 
					 | 
				
			||||||
            title_el = find_xpath_attr(
 | 
					 | 
				
			||||||
                itemdoc, './/{http://search.yahoo.com/mrss/}category',
 | 
					 | 
				
			||||||
                'scheme', 'urn:mtvn:video_title')
 | 
					 | 
				
			||||||
        if title_el is None:
 | 
					 | 
				
			||||||
            title_el = itemdoc.find('.//{http://search.yahoo.com/mrss/}title')
 | 
					 | 
				
			||||||
        if title_el is None:
 | 
					 | 
				
			||||||
            title_el = itemdoc.find('.//title')
 | 
					 | 
				
			||||||
            if title_el.text is None:
 | 
					 | 
				
			||||||
                title_el = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        title = title_el.text
 | 
					 | 
				
			||||||
        if title is None:
 | 
					 | 
				
			||||||
            raise ExtractorError('Could not find video title')
 | 
					 | 
				
			||||||
        title = title.strip()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        series = find_xpath_attr(
 | 
					 | 
				
			||||||
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
 | 
					 | 
				
			||||||
            'scheme', 'urn:mtvn:franchise')
 | 
					 | 
				
			||||||
        season = find_xpath_attr(
 | 
					 | 
				
			||||||
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
 | 
					 | 
				
			||||||
            'scheme', 'urn:mtvn:seasonN')
 | 
					 | 
				
			||||||
        episode = find_xpath_attr(
 | 
					 | 
				
			||||||
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
 | 
					 | 
				
			||||||
            'scheme', 'urn:mtvn:episodeN')
 | 
					 | 
				
			||||||
        series = series.text if series is not None else None
 | 
					 | 
				
			||||||
        season = season.text if season is not None else None
 | 
					 | 
				
			||||||
        episode = episode.text if episode is not None else None
 | 
					 | 
				
			||||||
        if season and episode:
 | 
					 | 
				
			||||||
            # episode number includes season, so remove it
 | 
					 | 
				
			||||||
            episode = re.sub(rf'^{season}', '', episode)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # This a short id that's used in the webpage urls
 | 
					 | 
				
			||||||
        mtvn_id = None
 | 
					 | 
				
			||||||
        mtvn_id_node = find_xpath_attr(itemdoc, './/{http://search.yahoo.com/mrss/}category',
 | 
					 | 
				
			||||||
                                       'scheme', 'urn:mtvn:id')
 | 
					 | 
				
			||||||
        if mtvn_id_node is not None:
 | 
					 | 
				
			||||||
            mtvn_id = mtvn_id_node.text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        formats = self._extract_video_formats(mediagen_doc, mtvn_id, video_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Some parts of complete video may be missing (e.g. missing Act 3 in
 | 
					 | 
				
			||||||
        # http://www.southpark.de/alle-episoden/s14e01-sexual-healing)
 | 
					 | 
				
			||||||
        if not formats:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'title': title,
 | 
					 | 
				
			||||||
            'formats': formats,
 | 
					 | 
				
			||||||
            'subtitles': self._extract_subtitles(mediagen_doc, mtvn_id),
 | 
					 | 
				
			||||||
            'id': video_id,
 | 
					 | 
				
			||||||
            'thumbnail': self._get_thumbnail_url(uri, itemdoc),
 | 
					 | 
				
			||||||
            'description': description,
 | 
					 | 
				
			||||||
            'duration': float_or_none(content_el.attrib.get('duration')),
 | 
					 | 
				
			||||||
            'timestamp': timestamp,
 | 
					 | 
				
			||||||
            'series': series,
 | 
					 | 
				
			||||||
            'season_number': int_or_none(season),
 | 
					 | 
				
			||||||
            'episode_number': int_or_none(episode),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if data is not None:
 | 
				
			||||||
 | 
					            headers['Content-Type'] = 'application/json'
 | 
				
			||||||
 | 
					            if isinstance(data, dict):
 | 
				
			||||||
 | 
					                data = json.dumps(data, separators=(',', ':')).encode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					        return self._download_json(
 | 
				
			||||||
        data = {'uri': uri}
 | 
					            f'https://auth.mtvnservices.com/{path}', display_id,
 | 
				
			||||||
        if self._LANG:
 | 
					            note=note or 'Calling authentication API', data=data,
 | 
				
			||||||
            data['lang'] = self._LANG
 | 
					            headers=headers, query={**self._get_auth_suite_data(config), **(query or {})})
 | 
				
			||||||
        return data
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_videos_info(self, uri, use_hls=True, url=None):
 | 
					    def _get_fresh_access_token(self, config, display_id=None, force_refresh=False):
 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					        resource_id = config['resourceId']
 | 
				
			||||||
        feed_url = self._get_feed_url(uri, url)
 | 
					        # resource_id should already be in _token_cache since _get_media_token is the caller
 | 
				
			||||||
        info_url = update_url_query(feed_url, self._get_feed_query(uri))
 | 
					        tokens = self._token_cache[resource_id]
 | 
				
			||||||
        return self._get_videos_info_from_url(info_url, video_id, use_hls)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_videos_info_from_url(self, url, video_id, use_hls=True):
 | 
					        access_token = tokens.get(self._ACCESS_TOKEN_KEY)
 | 
				
			||||||
        idoc = self._download_xml(
 | 
					        if not force_refresh and access_token and not self._jwt_is_expired(access_token):
 | 
				
			||||||
            url, video_id,
 | 
					            return access_token
 | 
				
			||||||
            'Downloading info', transform_source=fix_xml_ampersands)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        title = xpath_text(idoc, './channel/title')
 | 
					        if self._REFRESH_TOKEN_KEY not in tokens:
 | 
				
			||||||
        description = xpath_text(idoc, './channel/description')
 | 
					            response = self._call_auth_api(
 | 
				
			||||||
 | 
					                'accessToken', config, display_id, 'Retrieving auth tokens', data=b'')
 | 
				
			||||||
        entries = []
 | 
					 | 
				
			||||||
        for item in idoc.findall('.//item'):
 | 
					 | 
				
			||||||
            info = self._get_video_info(item, use_hls)
 | 
					 | 
				
			||||||
            if info:
 | 
					 | 
				
			||||||
                entries.append(info)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # TODO: should be multi-video
 | 
					 | 
				
			||||||
        return self.playlist_result(
 | 
					 | 
				
			||||||
            entries, playlist_title=title, playlist_description=description)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_triforce_mgid(self, webpage, data_zone=None, video_id=None):
 | 
					 | 
				
			||||||
        triforce_feed = self._parse_json(self._search_regex(
 | 
					 | 
				
			||||||
            r'triforceManifestFeed\s*=\s*({.+?})\s*;\s*\n', webpage,
 | 
					 | 
				
			||||||
            'triforce feed', default='{}'), video_id, fatal=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data_zone = self._search_regex(
 | 
					 | 
				
			||||||
            r'data-zone=(["\'])(?P<zone>.+?_lc_promo.*?)\1', webpage,
 | 
					 | 
				
			||||||
            'data zone', default=data_zone, group='zone')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        feed_url = try_get(
 | 
					 | 
				
			||||||
            triforce_feed, lambda x: x['manifest']['zones'][data_zone]['feed'],
 | 
					 | 
				
			||||||
            str)
 | 
					 | 
				
			||||||
        if not feed_url:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        feed = self._download_json(feed_url, video_id, fatal=False)
 | 
					 | 
				
			||||||
        if not feed:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return try_get(feed, lambda x: x['result']['data']['id'], str)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def _extract_child_with_type(parent, t):
 | 
					 | 
				
			||||||
        for c in parent['children']:
 | 
					 | 
				
			||||||
            if c.get('type') == t:
 | 
					 | 
				
			||||||
                return c
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _extract_mgid(self, webpage):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            # the url can be http://media.mtvnservices.com/fb/{mgid}.swf
 | 
					 | 
				
			||||||
            # or http://media.mtvnservices.com/{mgid}
 | 
					 | 
				
			||||||
            og_url = self._og_search_video_url(webpage)
 | 
					 | 
				
			||||||
            mgid = url_basename(og_url)
 | 
					 | 
				
			||||||
            if mgid.endswith('.swf'):
 | 
					 | 
				
			||||||
                mgid = mgid[:-4]
 | 
					 | 
				
			||||||
        except RegexNotFoundError:
 | 
					 | 
				
			||||||
            mgid = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if mgid is None or ':' not in mgid:
 | 
					 | 
				
			||||||
            mgid = self._search_regex(
 | 
					 | 
				
			||||||
                [r'data-mgid="(.*?)"', r'swfobject\.embedSWF\(".*?(mgid:.*?)"'],
 | 
					 | 
				
			||||||
                webpage, 'mgid', default=None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not mgid:
 | 
					 | 
				
			||||||
            sm4_embed = self._html_search_meta(
 | 
					 | 
				
			||||||
                'sm4:video:embed', webpage, 'sm4 embed', default='')
 | 
					 | 
				
			||||||
            mgid = self._search_regex(
 | 
					 | 
				
			||||||
                r'embed/(mgid:.+?)["\'&?/]', sm4_embed, 'mgid', default=None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not mgid:
 | 
					 | 
				
			||||||
            mgid = self._extract_triforce_mgid(webpage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not mgid:
 | 
					 | 
				
			||||||
            data = self._parse_json(self._search_regex(
 | 
					 | 
				
			||||||
                r'__DATA__\s*=\s*({.+?});', webpage, 'data'), None)
 | 
					 | 
				
			||||||
            main_container = self._extract_child_with_type(data, 'MainContainer')
 | 
					 | 
				
			||||||
            ab_testing = self._extract_child_with_type(main_container, 'ABTesting')
 | 
					 | 
				
			||||||
            video_player = self._extract_child_with_type(ab_testing or main_container, 'VideoPlayer')
 | 
					 | 
				
			||||||
            if video_player:
 | 
					 | 
				
			||||||
                mgid = try_get(video_player, lambda x: x['props']['media']['video']['config']['uri'])
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
                flex_wrapper = self._extract_child_with_type(ab_testing or main_container, 'FlexWrapper')
 | 
					            response = self._call_auth_api(
 | 
				
			||||||
                auth_suite_wrapper = self._extract_child_with_type(flex_wrapper, 'AuthSuiteWrapper')
 | 
					                'accessToken/refresh', config, display_id, 'Refreshing auth tokens',
 | 
				
			||||||
                player = self._extract_child_with_type(auth_suite_wrapper or flex_wrapper, 'Player')
 | 
					                data={'refreshToken': tokens[self._REFRESH_TOKEN_KEY]},
 | 
				
			||||||
                if player:
 | 
					                headers={'Authorization': f'Bearer {access_token}'})
 | 
				
			||||||
                    mgid = try_get(player, lambda x: x['props']['videoDetail']['mgid'])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not mgid:
 | 
					        tokens[self._ACCESS_TOKEN_KEY] = response['applicationAccessToken']
 | 
				
			||||||
            raise ExtractorError('Could not extract mgid')
 | 
					        tokens[self._REFRESH_TOKEN_KEY] = response['deviceRefreshToken']
 | 
				
			||||||
 | 
					        self.cache.store(self._CACHE_SECTION, resource_id, tokens)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return mgid
 | 
					        return tokens[self._ACCESS_TOKEN_KEY]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_media_token(self, video_config, config, display_id=None):
 | 
				
			||||||
 | 
					        resource_id = config['resourceId']
 | 
				
			||||||
 | 
					        if resource_id in self._token_cache:
 | 
				
			||||||
 | 
					            tokens = self._token_cache[resource_id]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            tokens = self._token_cache[resource_id] = self.cache.load(self._CACHE_SECTION, resource_id) or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        media_token = tokens.get(self._MEDIA_TOKEN_KEY)
 | 
				
			||||||
 | 
					        if media_token and not self._jwt_is_expired(media_token):
 | 
				
			||||||
 | 
					            return media_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        access_token = self._get_fresh_access_token(config, display_id)
 | 
				
			||||||
 | 
					        if not jwt_decode_hs256(access_token).get('accessMethods'):
 | 
				
			||||||
 | 
					            # MTVServices uses a custom AdobePass oauth flow which is incompatible with AdobePassIE
 | 
				
			||||||
 | 
					            mso_id = self.get_param('ap_mso')
 | 
				
			||||||
 | 
					            if not mso_id:
 | 
				
			||||||
 | 
					                raise ExtractorError(
 | 
				
			||||||
 | 
					                    'This video is only available for users of participating TV providers. '
 | 
				
			||||||
 | 
					                    'Use --ap-mso to specify Adobe Pass Multiple-system operator Identifier and pass '
 | 
				
			||||||
 | 
					                    'cookies from a browser session where you are signed-in to your provider.', expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auth_suite_data = json.dumps(
 | 
				
			||||||
 | 
					                self._get_auth_suite_data(config), separators=(',', ':')).encode()
 | 
				
			||||||
 | 
					            callback_url = update_url_query(config['callbackURL'], {
 | 
				
			||||||
 | 
					                'authSuiteData': urllib.parse.quote(base64.b64encode(auth_suite_data).decode()),
 | 
				
			||||||
 | 
					                'mvpdCode': mso_id,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            auth_url = self._call_auth_api(
 | 
				
			||||||
 | 
					                f'mvpd/{mso_id}/login', config, display_id,
 | 
				
			||||||
 | 
					                'Retrieving provider authentication URL',
 | 
				
			||||||
 | 
					                query={'callbackUrl': callback_url},
 | 
				
			||||||
 | 
					                headers={'Authorization': f'Bearer {access_token}'})['authenticationUrl']
 | 
				
			||||||
 | 
					            res = self._download_webpage_handle(auth_url, display_id, 'Downloading provider auth page')
 | 
				
			||||||
 | 
					            # XXX: The following "provider-specific code" likely only works if mso_id == Comcast_SSO
 | 
				
			||||||
 | 
					            # BEGIN provider-specific code
 | 
				
			||||||
 | 
					            redirect_url = self._search_json(
 | 
				
			||||||
 | 
					                r'initInterstitialRedirect\(', res[0], 'redirect JSON',
 | 
				
			||||||
 | 
					                display_id, transform_source=js_to_json)['continue']
 | 
				
			||||||
 | 
					            urlh = self._request_webpage(redirect_url, display_id, 'Requesting provider redirect page')
 | 
				
			||||||
 | 
					            authorization_code = parse_qs(urlh.url)['authorizationCode'][-1]
 | 
				
			||||||
 | 
					            # END provider-specific code
 | 
				
			||||||
 | 
					            self._call_auth_api(
 | 
				
			||||||
 | 
					                f'access/mvpd/{mso_id}', config, display_id,
 | 
				
			||||||
 | 
					                'Submitting authorization code to MTVNServices',
 | 
				
			||||||
 | 
					                query={'authorizationCode': authorization_code}, data=b'',
 | 
				
			||||||
 | 
					                headers={'Authorization': f'Bearer {access_token}'})
 | 
				
			||||||
 | 
					            access_token = self._get_fresh_access_token(config, display_id, force_refresh=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tokens[self._MEDIA_TOKEN_KEY] = self._call_auth_api(
 | 
				
			||||||
 | 
					            'mediaToken', config, display_id, 'Fetching media token', data={
 | 
				
			||||||
 | 
					                'content': {('id' if k == 'videoId' else k): v for k, v in video_config.items()},
 | 
				
			||||||
 | 
					                'resourceId': resource_id,
 | 
				
			||||||
 | 
					            }, headers={'Authorization': f'Bearer {access_token}'})['mediaToken']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cache.store(self._CACHE_SECTION, resource_id, tokens)
 | 
				
			||||||
 | 
					        return tokens[self._MEDIA_TOKEN_KEY]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        title = url_basename(url)
 | 
					        display_id = self._match_id(url)
 | 
				
			||||||
        webpage = self._download_webpage(url, title)
 | 
					 | 
				
			||||||
        mgid = self._extract_mgid(webpage)
 | 
					 | 
				
			||||||
        return self._get_videos_info(mgid, url=url)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = self._download_json(
 | 
				
			||||||
 | 
					                update_url(url, query=None), display_id,
 | 
				
			||||||
 | 
					                query={'json': 'true'})
 | 
				
			||||||
 | 
					        except ExtractorError as e:
 | 
				
			||||||
 | 
					            if isinstance(e.cause, HTTPError) and e.cause.status == 404 and not self.suitable(e.cause.response.url):
 | 
				
			||||||
 | 
					                self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
 | 
					        flex_wrapper = traverse_obj(data, (
 | 
				
			||||||
    IE_NAME = 'mtvservices:embedded'
 | 
					            'children', lambda _, v: v['type'] == 'MainContainer',
 | 
				
			||||||
    _VALID_URL = r'https?://media\.mtvnservices\.com/embed/(?P<mgid>.+?)(\?|/|$)'
 | 
					            (None, ('children', lambda _, v: v['type'] == 'AviaWrapper')),
 | 
				
			||||||
    _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//media\.mtvnservices\.com/embed/.+?)\1']
 | 
					            'children', lambda _, v: v['type'] == 'FlexWrapper', {dict}, any))
 | 
				
			||||||
 | 
					        video_detail = traverse_obj(flex_wrapper, (
 | 
				
			||||||
 | 
					            (None, ('children', lambda _, v: v['type'] == 'AuthSuiteWrapper')),
 | 
				
			||||||
 | 
					            'children', lambda _, v: v['type'] == 'Player',
 | 
				
			||||||
 | 
					            'props', 'videoDetail', {dict}, any))
 | 
				
			||||||
 | 
					        if not video_detail:
 | 
				
			||||||
 | 
					            video_detail = traverse_obj(data, (
 | 
				
			||||||
 | 
					                'children', ..., ('handleTVEAuthRedirection', None),
 | 
				
			||||||
 | 
					                'videoDetail', {dict}, any, {require('video detail')}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TEST = {
 | 
					        mgid = video_detail['mgid']
 | 
				
			||||||
        # From http://www.thewrap.com/peter-dinklage-sums-up-game-of-thrones-in-45-seconds-video/
 | 
					        video_id = mgid.rpartition(':')[2]
 | 
				
			||||||
        'url': 'http://media.mtvnservices.com/embed/mgid:uma:video:mtv.com:1043906/cp~vid%3D1043906%26uri%3Dmgid%3Auma%3Avideo%3Amtv.com%3A1043906',
 | 
					        service_url = traverse_obj(video_detail, ('videoServiceUrl', {url_or_none}, {update_url(query=None)}))
 | 
				
			||||||
        'md5': 'cb349b21a7897164cede95bd7bf3fbb9',
 | 
					        if not service_url:
 | 
				
			||||||
        'info_dict': {
 | 
					            raise ExtractorError('This content is no longer available', expected=True)
 | 
				
			||||||
            'id': '1043906',
 | 
					
 | 
				
			||||||
            'ext': 'mp4',
 | 
					        headers = {}
 | 
				
			||||||
            'title': 'Peter Dinklage Sums Up \'Game Of Thrones\' In 45 Seconds',
 | 
					        if video_detail.get('authRequired'):
 | 
				
			||||||
            'description': '"Sexy sexy sexy, stabby stabby stabby, beautiful language," says Peter Dinklage as he tries summarizing "Game of Thrones" in under a minute.',
 | 
					            # The vast majority of provider-locked content has been moved to Paramount+
 | 
				
			||||||
            'timestamp': 1400126400,
 | 
					            # BetIE is the only extractor that is currently known to reach this code path
 | 
				
			||||||
            'upload_date': '20140515',
 | 
					            video_config = traverse_obj(flex_wrapper, (
 | 
				
			||||||
        },
 | 
					                'children', lambda _, v: v['type'] == 'AuthSuiteWrapper',
 | 
				
			||||||
 | 
					                'props', 'videoConfig', {dict}, any, {require('video config')}))
 | 
				
			||||||
 | 
					            config = traverse_obj(data, (
 | 
				
			||||||
 | 
					                'props', 'authSuiteConfig', {dict}, {require('auth suite config')}))
 | 
				
			||||||
 | 
					            headers['X-VIA-TVE-MEDIATOKEN'] = self._get_media_token(video_config, config, display_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stream_info = self._download_json(
 | 
				
			||||||
 | 
					            service_url, video_id, 'Downloading API JSON', 'Unable to download API JSON',
 | 
				
			||||||
 | 
					            query={'clientPlatform': 'desktop'}, headers=headers)['stitchedstream']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manifest_type = stream_info['manifesttype']
 | 
				
			||||||
 | 
					        if manifest_type == 'hls':
 | 
				
			||||||
 | 
					            formats, subtitles = self._extract_m3u8_formats_and_subtitles(
 | 
				
			||||||
 | 
					                stream_info['source'], video_id, 'mp4', m3u8_id=manifest_type)
 | 
				
			||||||
 | 
					        elif manifest_type == 'dash':
 | 
				
			||||||
 | 
					            formats, subtitles = self._extract_mpd_formats_and_subtitles(
 | 
				
			||||||
 | 
					                stream_info['source'], video_id, mpd_id=manifest_type)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.raise_no_formats(f'Unsupported manifest type "{manifest_type}"')
 | 
				
			||||||
 | 
					            formats, subtitles = [], {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            **traverse_obj(video_detail, {
 | 
				
			||||||
 | 
					                'title': ('title', {str}),
 | 
				
			||||||
 | 
					                'channel': ('channel', 'name', {str}),
 | 
				
			||||||
 | 
					                'thumbnails': ('images', ..., {'url': ('url', {url_or_none})}),
 | 
				
			||||||
 | 
					                'description': (('fullDescription', 'description'), {str}, any),
 | 
				
			||||||
 | 
					                'series': ('parentEntity', 'title', {str}),
 | 
				
			||||||
 | 
					                'season_number': ('seasonNumber', {int_or_none}),
 | 
				
			||||||
 | 
					                'episode_number': ('episodeAiringOrder', {int_or_none}),
 | 
				
			||||||
 | 
					                'duration': ('duration', 'milliseconds', {float_or_none(scale=1000)}),
 | 
				
			||||||
 | 
					                'timestamp': ((
 | 
				
			||||||
 | 
					                    ('originalPublishDate', {parse_iso8601}),
 | 
				
			||||||
 | 
					                    ('publishDate', 'timestamp', {int_or_none})), any),
 | 
				
			||||||
 | 
					                'release_timestamp': ((
 | 
				
			||||||
 | 
					                    ('originalAirDate', {parse_iso8601}),
 | 
				
			||||||
 | 
					                    ('airDate', 'timestamp', {int_or_none})), any),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            'id': video_id,
 | 
				
			||||||
 | 
					            'display_id': display_id,
 | 
				
			||||||
 | 
					            'formats': formats,
 | 
				
			||||||
 | 
					            'subtitles': subtitles,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_feed_url(self, uri, url=None):
 | 
					 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					 | 
				
			||||||
        config = self._download_json(
 | 
					 | 
				
			||||||
            f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge', video_id)
 | 
					 | 
				
			||||||
        return self._remove_template_parameter(config['feedWithQueryParams'])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					class MTVIE(MTVServicesBaseIE):
 | 
				
			||||||
        mobj = self._match_valid_url(url)
 | 
					 | 
				
			||||||
        mgid = mobj.group('mgid')
 | 
					 | 
				
			||||||
        return self._get_videos_info(mgid)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'mtv'
 | 
					    IE_NAME = 'mtv'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?mtv\.com/(?:video-clips|(?:full-)?episodes)/(?P<id>[^/?#.]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?mtv\.com/(?:video-clips|episodes)/(?P<id>[\da-z]{6})'
 | 
				
			||||||
    _FEED_URL = 'http://www.mtv.com/feeds/mrss/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.mtv.com/video-clips/vl8qof/unlocking-the-truth-trailer',
 | 
					        'url': 'https://www.mtv.com/video-clips/syolsj',
 | 
				
			||||||
        'md5': '1edbcdf1e7628e414a8c5dcebca3d32b',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '5e14040d-18a4-47c4-a582-43ff602de88e',
 | 
					            'id': '213ea7f8-bac7-4a43-8cd5-8d8cb8c8160f',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Unlocking The Truth|July 18, 2016|1|101|Trailer',
 | 
					            'display_id': 'syolsj',
 | 
				
			||||||
            'description': '"Unlocking the Truth" premieres August 17th at 11/10c.',
 | 
					            'title': 'The Challenge: Vets & New Threats',
 | 
				
			||||||
            'timestamp': 1468846800,
 | 
					            'description': 'md5:c4d2e90a5fff6463740fbf96b2bb6a41',
 | 
				
			||||||
            'upload_date': '20160718',
 | 
					            'duration': 95.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref',
 | 
				
			||||||
 | 
					            'series': 'The Challenge',
 | 
				
			||||||
 | 
					            'season': 'Season 41',
 | 
				
			||||||
 | 
					            'season_number': 41,
 | 
				
			||||||
 | 
					            'episode': 'Episode 0',
 | 
				
			||||||
 | 
					            'episode_number': 0,
 | 
				
			||||||
 | 
					            'timestamp': 1753945200,
 | 
				
			||||||
 | 
					            'upload_date': '20250731',
 | 
				
			||||||
 | 
					            'release_timestamp': 1753945200,
 | 
				
			||||||
 | 
					            'release_date': '20250731',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.mtv.com/full-episodes/94tujl/unlocking-the-truth-gates-of-hell-season-1-ep-101',
 | 
					        'url': 'https://www.mtv.com/episodes/uzvigh',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'info_dict': {
 | 
				
			||||||
    }, {
 | 
					            'id': '364e8b9e-e415-11ef-b405-16fff45bc035',
 | 
				
			||||||
        'url': 'http://www.mtv.com/episodes/g8xu7q/teen-mom-2-breaking-the-wall-season-7-ep-713',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
        'only_matching': True,
 | 
					            'display_id': 'uzvigh',
 | 
				
			||||||
 | 
					            'title': 'CT Tamburello and Johnny Bananas',
 | 
				
			||||||
 | 
					            'description': 'md5:364cea52001e9c13f92784e3365c6606',
 | 
				
			||||||
 | 
					            'channel': 'MTV',
 | 
				
			||||||
 | 
					            'duration': 1260.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref',
 | 
				
			||||||
 | 
					            'series': 'Ridiculousness',
 | 
				
			||||||
 | 
					            'season': 'Season 47',
 | 
				
			||||||
 | 
					            'season_number': 47,
 | 
				
			||||||
 | 
					            'episode': 'Episode 19',
 | 
				
			||||||
 | 
					            'episode_number': 19,
 | 
				
			||||||
 | 
					            'timestamp': 1753318800,
 | 
				
			||||||
 | 
					            'upload_date': '20250724',
 | 
				
			||||||
 | 
					            'release_timestamp': 1753318800,
 | 
				
			||||||
 | 
					            'release_date': '20250724',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVJapanIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'mtvjapan'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?mtvjapan\.com/videos/(?P<id>[0-9a-z]+)'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TEST = {
 | 
					 | 
				
			||||||
        'url': 'http://www.mtvjapan.com/videos/prayht/fresh-info-cadillac-escalade',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'bc01da03-6fe5-4284-8880-f291f4e368f5',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': '【Fresh Info】Cadillac ESCALADE Sport Edition',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['JP']
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'arcEp': 'mtvjapan.com',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVVideoIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'mtv:video'
 | 
					 | 
				
			||||||
    _VALID_URL = r'''(?x)^https?://
 | 
					 | 
				
			||||||
        (?:(?:www\.)?mtv\.com/videos/.+?/(?P<videoid>[0-9]+)/[^/]+$|
 | 
					 | 
				
			||||||
           m\.mtv\.com/videos/video\.rbml\?.*?id=(?P<mgid>[^&]+))'''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://www.mtv.com/player/embed/AS3/rss/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            'url': 'http://www.mtv.com/videos/misc/853555/ours-vh1-storytellers.jhtml',
 | 
					 | 
				
			||||||
            'md5': '850f3f143316b1e71fa56a4edfd6e0f8',
 | 
					 | 
				
			||||||
            'info_dict': {
 | 
					 | 
				
			||||||
                'id': '853555',
 | 
					 | 
				
			||||||
                'ext': 'mp4',
 | 
					 | 
				
			||||||
                'title': 'Taylor Swift - "Ours (VH1 Storytellers)"',
 | 
					 | 
				
			||||||
                'description': 'Album: Taylor Swift performs "Ours" for VH1 Storytellers at Harvey Mudd College.',
 | 
					 | 
				
			||||||
                'timestamp': 1352610000,
 | 
					 | 
				
			||||||
                'upload_date': '20121111',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_thumbnail_url(self, uri, itemdoc):
 | 
					 | 
				
			||||||
        return 'http://mtv.mtvnimages.com/uri/' + uri
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        mobj = self._match_valid_url(url)
 | 
					 | 
				
			||||||
        video_id = mobj.group('videoid')
 | 
					 | 
				
			||||||
        uri = mobj.groupdict().get('mgid')
 | 
					 | 
				
			||||||
        if uri is None:
 | 
					 | 
				
			||||||
            webpage = self._download_webpage(url, video_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Some videos come from Vevo.com
 | 
					 | 
				
			||||||
            m_vevo = re.search(
 | 
					 | 
				
			||||||
                r'(?s)isVevoVideo = true;.*?vevoVideoId = "(.*?)";', webpage)
 | 
					 | 
				
			||||||
            if m_vevo:
 | 
					 | 
				
			||||||
                vevo_id = m_vevo.group(1)
 | 
					 | 
				
			||||||
                self.to_screen(f'Vevo video detected: {vevo_id}')
 | 
					 | 
				
			||||||
                return self.url_result(f'vevo:{vevo_id}', ie='Vevo')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            uri = self._html_search_regex(r'/uri/(.*?)\?', webpage, 'uri')
 | 
					 | 
				
			||||||
        return self._get_videos_info(uri)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVDEIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    _WORKING = False
 | 
					 | 
				
			||||||
    IE_NAME = 'mtv.de'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?mtv\.de/(?:musik/videoclips|folgen|news)/(?P<id>[0-9a-z]+)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.de/musik/videoclips/2gpnv7/Traum',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'd5d472bc-f5b7-11e5-bffd-a4badb20dab5',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Traum',
 | 
					 | 
				
			||||||
            'description': 'Traum',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            # rtmp download
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'skip': 'Blocked at Travis CI',
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # mediagen URL without query (e.g. http://videos.mtvnn.com/mediagen/e865da714c166d18d6f80893195fcb97)
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.de/folgen/6b1ylu/teen-mom-2-enthuellungen-S5-F1',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '1e5a878b-31c5-11e7-a442-0e40cf2fc285',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Teen Mom 2',
 | 
					 | 
				
			||||||
            'description': 'md5:dc65e357ef7e1085ed53e9e9d83146a7',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            # rtmp download
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'skip': 'Blocked at Travis CI',
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.de/news/glolix/77491-mtv-movies-spotlight--pixels--teil-3',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'local_playlist-4e760566473c4c8c5344',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Article_mtv-movies-spotlight-pixels-teil-3_short-clips_part1',
 | 
					 | 
				
			||||||
            'description': 'MTV Movies Supercut',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            # rtmp download
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'skip': 'Das Video kann zur Zeit nicht abgespielt werden.',
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['DE']
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'arcEp': 'mtv.de',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVItaliaIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'mtv.it'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:episodi|video|musica)/(?P<id>[0-9a-z]+)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.it/episodi/24bqab/mario-una-serie-di-maccio-capatonda-cavoli-amario-episodio-completo-S1-E1',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '0f0fc78e-45fc-4cce-8f24-971c25477530',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Cavoli amario (episodio completo)',
 | 
					 | 
				
			||||||
            'description': 'md5:4962bccea8fed5b7c03b295ae1340660',
 | 
					 | 
				
			||||||
            'series': 'Mario - Una Serie Di Maccio Capatonda',
 | 
					 | 
				
			||||||
            'season_number': 1,
 | 
					 | 
				
			||||||
            'episode_number': 1,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['IT']
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'arcEp': 'mtv.it',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MTVItaliaProgrammaIE(MTVItaliaIE):  # XXX: Do not subclass from concrete IE
 | 
					 | 
				
			||||||
    IE_NAME = 'mtv.it:programma'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:programmi|playlist)/(?P<id>[0-9a-z]+)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        # program page: general
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.it/programmi/s2rppv/mario-una-serie-di-maccio-capatonda',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'a6f155bc-8220-4640-aa43-9b95f64ffa3d',
 | 
					 | 
				
			||||||
            'title': 'Mario - Una Serie Di Maccio Capatonda',
 | 
					 | 
				
			||||||
            'description': 'md5:72fbffe1f77ccf4e90757dd4e3216153',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_count': 2,
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # program page: specific season
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.it/programmi/d9ncjf/mario-una-serie-di-maccio-capatonda-S2',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '4deeb5d8-f272-490c-bde2-ff8d261c6dd1',
 | 
					 | 
				
			||||||
            'title': 'Mario - Una Serie Di Maccio Capatonda - Stagione 2',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_count': 34,
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # playlist page + redirect
 | 
					 | 
				
			||||||
        'url': 'http://www.mtv.it/playlist/sexy-videos/ilctal',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'dee8f9ee-756d-493b-bf37-16d1d2783359',
 | 
					 | 
				
			||||||
            'title': 'Sexy Videos',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_mincount': 145,
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['IT']
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://www.mtv.it/feeds/triforce/manifest/v8'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_entries(self, title, url):
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            pg = self._search_regex(r'/(\d+)$', url, 'entries', '1')
 | 
					 | 
				
			||||||
            entries = self._download_json(url, title, f'page {pg}')
 | 
					 | 
				
			||||||
            url = try_get(
 | 
					 | 
				
			||||||
                entries, lambda x: x['result']['nextPageURL'], str)
 | 
					 | 
				
			||||||
            entries = try_get(
 | 
					 | 
				
			||||||
                entries, (
 | 
					 | 
				
			||||||
                    lambda x: x['result']['data']['items'],
 | 
					 | 
				
			||||||
                    lambda x: x['result']['data']['seasons']),
 | 
					 | 
				
			||||||
                list)
 | 
					 | 
				
			||||||
            for entry in entries or []:
 | 
					 | 
				
			||||||
                if entry.get('canonicalURL'):
 | 
					 | 
				
			||||||
                    yield self.url_result(entry['canonicalURL'])
 | 
					 | 
				
			||||||
            if not url:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        query = {'url': url}
 | 
					 | 
				
			||||||
        info_url = update_url_query(self._FEED_URL, query)
 | 
					 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					 | 
				
			||||||
        info = self._download_json(info_url, video_id).get('manifest')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        redirect = try_get(
 | 
					 | 
				
			||||||
            info, lambda x: x['newLocation']['url'], str)
 | 
					 | 
				
			||||||
        if redirect:
 | 
					 | 
				
			||||||
            return self.url_result(redirect)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        title = info.get('title')
 | 
					 | 
				
			||||||
        video_id = try_get(
 | 
					 | 
				
			||||||
            info, lambda x: x['reporting']['itemId'], str)
 | 
					 | 
				
			||||||
        parent_id = try_get(
 | 
					 | 
				
			||||||
            info, lambda x: x['reporting']['parentId'], str)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        playlist_url = current_url = None
 | 
					 | 
				
			||||||
        for z in (info.get('zones') or {}).values():
 | 
					 | 
				
			||||||
            if z.get('moduleName') in ('INTL_M304', 'INTL_M209'):
 | 
					 | 
				
			||||||
                info_url = z.get('feed')
 | 
					 | 
				
			||||||
            if z.get('moduleName') in ('INTL_M308', 'INTL_M317'):
 | 
					 | 
				
			||||||
                playlist_url = playlist_url or z.get('feed')
 | 
					 | 
				
			||||||
            if z.get('moduleName') in ('INTL_M300',):
 | 
					 | 
				
			||||||
                current_url = current_url or z.get('feed')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not info_url:
 | 
					 | 
				
			||||||
            raise ExtractorError('No info found')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if video_id == parent_id:
 | 
					 | 
				
			||||||
            video_id = self._search_regex(
 | 
					 | 
				
			||||||
                r'([^\/]+)/[^\/]+$', info_url, 'video_id')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        info = self._download_json(info_url, video_id, 'Show infos')
 | 
					 | 
				
			||||||
        info = try_get(info, lambda x: x['result']['data'], dict)
 | 
					 | 
				
			||||||
        title = title or try_get(
 | 
					 | 
				
			||||||
            info, (
 | 
					 | 
				
			||||||
                lambda x: x['title'],
 | 
					 | 
				
			||||||
                lambda x: x['headline']),
 | 
					 | 
				
			||||||
            str)
 | 
					 | 
				
			||||||
        description = try_get(info, lambda x: x['content'], str)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if current_url:
 | 
					 | 
				
			||||||
            season = try_get(
 | 
					 | 
				
			||||||
                self._download_json(playlist_url, video_id, 'Seasons info'),
 | 
					 | 
				
			||||||
                lambda x: x['result']['data'], dict)
 | 
					 | 
				
			||||||
            current = try_get(
 | 
					 | 
				
			||||||
                season, lambda x: x['currentSeason'], str)
 | 
					 | 
				
			||||||
            seasons = try_get(
 | 
					 | 
				
			||||||
                season, lambda x: x['seasons'], list) or []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if current in [s.get('eTitle') for s in seasons]:
 | 
					 | 
				
			||||||
                playlist_url = current_url
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        title = re.sub(
 | 
					 | 
				
			||||||
            r'[-|]\s*(?:mtv\s*italia|programma|playlist)',
 | 
					 | 
				
			||||||
            '', title, flags=re.IGNORECASE).strip()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.playlist_result(
 | 
					 | 
				
			||||||
            self._get_entries(title, playlist_url),
 | 
					 | 
				
			||||||
            video_id, title, description)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,224 +1,48 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					from .mtv import MTVServicesBaseIE
 | 
				
			||||||
from ..utils import update_url_query
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NickIE(MTVServicesInfoExtractor):
 | 
					class NickIE(MTVServicesBaseIE):
 | 
				
			||||||
    IE_NAME = 'nick.com'
 | 
					    IE_NAME = 'nick.com'
 | 
				
			||||||
    _VALID_URL = r'https?://(?P<domain>(?:www\.)?nick(?:jr)?\.com)/(?:[^/]+/)?(?P<type>videos/clip|[^/]+/videos|episodes/[^/]+)/(?P<id>[^/?#.]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?nick\.com/(?:video-clips|episodes)/(?P<id>[\da-z]{6})'
 | 
				
			||||||
    _FEED_URL = 'http://udat.mtvnservices.com/service1/dispatch.htm'
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['US']
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'https://www.nick.com/episodes/sq47rw/spongebob-squarepants-a-place-for-pets-lockdown-for-love-season-13-ep-1',
 | 
					        'url': 'https://www.nick.com/episodes/u3smw8/wylde-pak-best-summer-ever-season-1-ep-1',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'description': 'md5:0650a9eb88955609d5c1d1c79292e234',
 | 
					            'id': 'eb9d4db0-274a-11ef-a913-0e37995d42c9',
 | 
				
			||||||
            'title': 'A Place for Pets/Lockdown for Love',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist': [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                'md5': 'cb8a2afeafb7ae154aca5a64815ec9d6',
 | 
					 | 
				
			||||||
                'info_dict': {
 | 
					 | 
				
			||||||
                    'id': '85ee8177-d6ce-48f8-9eee-a65364f8a6df',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
                    'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S1',
 | 
					            'display_id': 'u3smw8',
 | 
				
			||||||
                    'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
 | 
					            'title': 'Best Summer Ever?',
 | 
				
			||||||
 | 
					            'description': 'md5:c737a0ade3fbc09d569c3b3d029a7792',
 | 
				
			||||||
 | 
					            'channel': 'Nickelodeon',
 | 
				
			||||||
 | 
					            'duration': 1296.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://assets\.nick\.com/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'Wylde Pak',
 | 
				
			||||||
 | 
					            'season': 'Season 1',
 | 
				
			||||||
 | 
					            'season_number': 1,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 1746100800,
 | 
				
			||||||
 | 
					            'upload_date': '20250501',
 | 
				
			||||||
 | 
					            'release_timestamp': 1746100800,
 | 
				
			||||||
 | 
					            'release_date': '20250501',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
            },
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                'md5': '839a04f49900a1fcbf517020d94e0737',
 | 
					 | 
				
			||||||
                'info_dict': {
 | 
					 | 
				
			||||||
                    'id': '2e2a9960-8fd4-411d-868b-28eb1beb7fae',
 | 
					 | 
				
			||||||
                    'ext': 'mp4',
 | 
					 | 
				
			||||||
                    'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S2',
 | 
					 | 
				
			||||||
                    'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                'md5': 'f1145699f199770e2919ee8646955d46',
 | 
					 | 
				
			||||||
                'info_dict': {
 | 
					 | 
				
			||||||
                    'id': 'dc91c304-6876-40f7-84a6-7aece7baa9d0',
 | 
					 | 
				
			||||||
                    'ext': 'mp4',
 | 
					 | 
				
			||||||
                    'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S3',
 | 
					 | 
				
			||||||
                    'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                'md5': 'd463116875aee2585ee58de3b12caebd',
 | 
					 | 
				
			||||||
                'info_dict': {
 | 
					 | 
				
			||||||
                    'id': '5d929486-cf4c-42a1-889a-6e0d183a101a',
 | 
					 | 
				
			||||||
                    'ext': 'mp4',
 | 
					 | 
				
			||||||
                    'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S4',
 | 
					 | 
				
			||||||
                    'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.nickjr.com/blues-clues-and-you/videos/blues-clues-and-you-original-209-imagination-station/',
 | 
					        'url': 'https://www.nick.com/video-clips/0p4706/spongebob-squarepants-spongebob-loving-the-krusty-krab-for-7-minutes',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '31631529-2fc5-430b-b2ef-6a74b4609abd',
 | 
					            'id': '4aac2228-5295-4076-b986-159513cf4ce4',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'description': 'md5:9d65a66df38e02254852794b2809d1cf',
 | 
					            'display_id': '0p4706',
 | 
				
			||||||
            'title': 'Blue\'s Imagination Station',
 | 
					            'title': 'SpongeBob Loving the Krusty Krab for 7 Minutes!',
 | 
				
			||||||
 | 
					            'description': 'md5:72bf59babdf4e6d642187502864e111d',
 | 
				
			||||||
 | 
					            'duration': 423.423,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://assets\.nick\.com/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'SpongeBob SquarePants',
 | 
				
			||||||
 | 
					            'season': 'Season 0',
 | 
				
			||||||
 | 
					            'season_number': 0,
 | 
				
			||||||
 | 
					            'episode': 'Episode 0',
 | 
				
			||||||
 | 
					            'episode_number': 0,
 | 
				
			||||||
 | 
					            'timestamp': 1663819200,
 | 
				
			||||||
 | 
					            'upload_date': '20220922',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'skip': 'Not accessible?',
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'feed': 'nick_arc_player_prime',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        domain, video_type, display_id = self._match_valid_url(url).groups()
 | 
					 | 
				
			||||||
        if video_type.startswith('episodes'):
 | 
					 | 
				
			||||||
            return super()._real_extract(url)
 | 
					 | 
				
			||||||
        video_data = self._download_json(
 | 
					 | 
				
			||||||
            f'http://{domain}/data/video.endLevel.json',
 | 
					 | 
				
			||||||
            display_id, query={
 | 
					 | 
				
			||||||
                'urlKey': display_id,
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        return self._get_videos_info(video_data['player'] + video_data['id'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NickBrIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'nickelodeon:br'
 | 
					 | 
				
			||||||
    _VALID_URL = r'''(?x)
 | 
					 | 
				
			||||||
                    https?://
 | 
					 | 
				
			||||||
                        (?:
 | 
					 | 
				
			||||||
                            (?P<domain>(?:www\.)?nickjr|mundonick\.uol)\.com\.br|
 | 
					 | 
				
			||||||
                            (?:www\.)?nickjr\.[a-z]{2}|
 | 
					 | 
				
			||||||
                            (?:www\.)?nickelodeonjunior\.fr
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        /(?:programas/)?[^/]+/videos/(?:episodios/)?(?P<id>[^/?\#.]+)
 | 
					 | 
				
			||||||
                    '''
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.nickjr.com.br/patrulha-canina/videos/210-labirinto-de-pipoca/',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://mundonick.uol.com.br/programas/the-loud-house/videos/muitas-irmas/7ljo9j',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickjr.nl/paw-patrol/videos/311-ge-wol-dig-om-terug-te-zijn/',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickjr.de/blaze-und-die-monster-maschinen/videos/f6caaf8f-e4e8-4cc1-b489-9380d6dcd059/',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeonjunior.fr/paw-patrol-la-pat-patrouille/videos/episode-401-entier-paw-patrol/',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        domain, display_id = self._match_valid_url(url).groups()
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, display_id)
 | 
					 | 
				
			||||||
        uri = self._search_regex(
 | 
					 | 
				
			||||||
            r'data-(?:contenturi|mgid)="([^"]+)', webpage, 'mgid')
 | 
					 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					 | 
				
			||||||
        config = self._download_json(
 | 
					 | 
				
			||||||
            'http://media.mtvnservices.com/pmt/e1/access/index.html',
 | 
					 | 
				
			||||||
            video_id, query={
 | 
					 | 
				
			||||||
                'uri': uri,
 | 
					 | 
				
			||||||
                'configtype': 'edge',
 | 
					 | 
				
			||||||
            }, headers={
 | 
					 | 
				
			||||||
                'Referer': url,
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        info_url = self._remove_template_parameter(config['feedWithQueryParams'])
 | 
					 | 
				
			||||||
        if info_url == 'None':
 | 
					 | 
				
			||||||
            if domain.startswith('www.'):
 | 
					 | 
				
			||||||
                domain = domain[4:]
 | 
					 | 
				
			||||||
            content_domain = {
 | 
					 | 
				
			||||||
                'mundonick.uol': 'mundonick.com.br',
 | 
					 | 
				
			||||||
                'nickjr': 'br.nickelodeonjunior.tv',
 | 
					 | 
				
			||||||
            }[domain]
 | 
					 | 
				
			||||||
            query = {
 | 
					 | 
				
			||||||
                'mgid': uri,
 | 
					 | 
				
			||||||
                'imageEp': content_domain,
 | 
					 | 
				
			||||||
                'arcEp': content_domain,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if domain == 'nickjr.com.br':
 | 
					 | 
				
			||||||
                query['ep'] = 'c4b16088'
 | 
					 | 
				
			||||||
            info_url = update_url_query(
 | 
					 | 
				
			||||||
                'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed', query)
 | 
					 | 
				
			||||||
        return self._get_videos_info_from_url(info_url, video_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NickDeIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'nick.de'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<host>nick\.(?:de|com\.pl|ch)|nickelodeon\.(?:nl|be|at|dk|no|se))/[^/]+/(?:[^/]+/)*(?P<id>[^/?#&]+)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.nick.de/playlist/3773-top-videos/videos/episode/17306-zu-wasser-und-zu-land-rauchende-erdnusse',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nick.de/shows/342-icarly',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.nl/shows/474-spongebob/videos/17403-een-kijkje-in-de-keuken-met-sandy-van-binnenuit',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.at/playlist/3773-top-videos/videos/episode/77993-das-letzte-gefecht',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nick.com.pl/seriale/474-spongebob-kanciastoporty/wideo/17412-teatr-to-jest-to-rodeo-oszolom',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.no/program/2626-bulderhuset/videoer/90947-femteklasse-veronica-vs-vanzilla',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.dk/serier/2626-hojs-hus/videoer/761-tissepause',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.se/serier/2626-lugn-i-stormen/videos/998-',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nick.ch/shows/2304-adventure-time-abenteuerzeit-mit-finn-und-jake',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.be/afspeellijst/4530-top-videos/videos/episode/73917-inval-broodschapper-lariekoek-arie',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_url(self, uri, url=None):
 | 
					 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					 | 
				
			||||||
        config = self._download_json(
 | 
					 | 
				
			||||||
            f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id)
 | 
					 | 
				
			||||||
        return self._remove_template_parameter(config['feedWithQueryParams'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NickRuIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'nickelodeonru'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)nickelodeon\.(?:ru|fr|es|pt|ro|hu|com\.tr)/[^/]+/(?:[^/]+/)*(?P<id>[^/?#&]+)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.ru/shows/henrydanger/videos/episodes/3-sezon-15-seriya-licenziya-na-polyot/pmomfb#playlist/7airc6',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.ru/videos/smotri-na-nickelodeon-v-iyule/g9hvh7',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.fr/programmes/bob-l-eponge/videos/le-marathon-de-booh-kini-bottom-mardi-31-octobre/nfn7z0',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.es/videos/nickelodeon-consejos-tortitas/f7w7xy',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.pt/series/spongebob-squarepants/videos/a-bolha-de-tinta-gigante/xutq1b',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.ro/emisiuni/shimmer-si-shine/video/nahal-din-bomboane/uw5u2k',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.hu/musorok/spongyabob-kockanadrag/videok/episodes/buborekfujas-az-elszakadt-nadrag/q57iob#playlist/k6te4y',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.nickelodeon.com.tr/programlar/sunger-bob/videolar/kayip-yatak/mgqbjy',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _real_extract(self, url):
 | 
					 | 
				
			||||||
        video_id = self._match_id(url)
 | 
					 | 
				
			||||||
        webpage = self._download_webpage(url, video_id)
 | 
					 | 
				
			||||||
        mgid = self._extract_mgid(webpage, url)
 | 
					 | 
				
			||||||
        return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}')
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,58 +1,94 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					from .mtv import MTVServicesBaseIE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SouthParkIE(MTVServicesInfoExtractor):
 | 
					class SouthParkIE(MTVServicesBaseIE):
 | 
				
			||||||
    IE_NAME = 'southpark.cc.com'
 | 
					    IE_NAME = 'southpark.cc.com'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<url>southpark(?:\.cc|studios)\.com/((?:video-)?clips|(?:full-)?episodes|collections)/(?P<id>.+?)(\?|#|$))'
 | 
					    _VALID_URL = r'https?://(?:www\.)?southpark(?:\.cc|studios)\.com/(?:video-clips|episodes|collections)/(?P<id>[^?#]+)'
 | 
				
			||||||
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'https://southpark.cc.com/video-clips/d7wr06/south-park-you-all-agreed-to-counseling',
 | 
					        'url': 'https://southpark.cc.com/video-clips/d7wr06/south-park-you-all-agreed-to-counseling',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '31929ad5-8269-11eb-8774-70df2f866ace',
 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'display_id': 'd7wr06/south-park-you-all-agreed-to-counseling',
 | 
				
			||||||
            'title': 'You All Agreed to Counseling',
 | 
					            'title': 'You All Agreed to Counseling',
 | 
				
			||||||
            'description': 'Kenny, Cartman, Stan, and Kyle visit Mr. Mackey and ask for his help getting Mrs. Nelson to come back. Mr. Mackey reveals the only way to get things back to normal is to get the teachers vaccinated.',
 | 
					            'description': 'md5:01f78fb306c7042f3f05f3c78edfc212',
 | 
				
			||||||
 | 
					            'duration': 134.552,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 24',
 | 
				
			||||||
 | 
					            'season_number': 24,
 | 
				
			||||||
 | 
					            'episode': 'Episode 2',
 | 
				
			||||||
 | 
					            'episode_number': 2,
 | 
				
			||||||
            'timestamp': 1615352400,
 | 
					            'timestamp': 1615352400,
 | 
				
			||||||
            'upload_date': '20210310',
 | 
					            'upload_date': '20210310',
 | 
				
			||||||
 | 
					            'release_timestamp': 1615352400,
 | 
				
			||||||
 | 
					            'release_date': '20210310',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://southpark.cc.com/collections/7758/fan-favorites/1',
 | 
					        'url': 'https://southpark.cc.com/episodes/940f8z/south-park-cartman-gets-an-anal-probe-season-1-ep-1',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '5fb8887e-ecfd-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'display_id': '940f8z/south-park-cartman-gets-an-anal-probe-season-1-ep-1',
 | 
				
			||||||
 | 
					            'title': 'Cartman Gets An Anal Probe',
 | 
				
			||||||
 | 
					            'description': 'md5:964e1968c468545752feef102b140300',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 1319.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 1',
 | 
				
			||||||
 | 
					            'season_number': 1,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 871473600,
 | 
				
			||||||
 | 
					            'upload_date': '19970813',
 | 
				
			||||||
 | 
					            'release_timestamp': 871473600,
 | 
				
			||||||
 | 
					            'release_date': '19970813',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://southpark.cc.com/collections/dejukt/south-park-best-of-mr-mackey/tphx9j',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'https://www.southparkstudios.com/episodes/h4o269/south-park-stunning-and-brave-season-19-ep-1',
 | 
					        'url': 'https://www.southparkstudios.com/episodes/h4o269/south-park-stunning-and-brave-season-19-ep-1',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'accountOverride': 'intl.mtvi.com',
 | 
					 | 
				
			||||||
            'arcEp': 'shared.southpark.global',
 | 
					 | 
				
			||||||
            'ep': '90877963',
 | 
					 | 
				
			||||||
            'imageEp': 'shared.southpark.global',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SouthParkEsIE(MTVServicesBaseIE):
 | 
				
			||||||
class SouthParkEsIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
					 | 
				
			||||||
    IE_NAME = 'southpark.cc.com:español'
 | 
					    IE_NAME = 'southpark.cc.com:español'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<url>southpark\.cc\.com/es/episodios/(?P<id>.+?)(\?|#|$))'
 | 
					    _VALID_URL = r'https?://(?:www\.)?southpark\.cc\.com/es/episodios/(?P<id>[^?#]+)'
 | 
				
			||||||
    _LANG = 'es'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://southpark.cc.com/es/episodios/s01e01-cartman-consigue-una-sonda-anal#source=351c1323-0b96-402d-a8b9-40d01b2e9bde&position=1&sort=!airdate',
 | 
					        'url': 'https://southpark.cc.com/es/episodios/er4a32/south-park-aumento-de-peso-4000-temporada-1-ep-2',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'Cartman Consigue Una Sonda Anal',
 | 
					            'id': '5fb94f0c-ecfd-11e0-aca6-0026b9414f30',
 | 
				
			||||||
            'description': 'Cartman Consigue Una Sonda Anal',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'display_id': 'er4a32/south-park-aumento-de-peso-4000-temporada-1-ep-2',
 | 
				
			||||||
 | 
					            'title': 'Aumento de peso 4000',
 | 
				
			||||||
 | 
					            'description': 'md5:a939b4819ea74c245a0cde180de418c0',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 1320.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 1',
 | 
				
			||||||
 | 
					            'season_number': 1,
 | 
				
			||||||
 | 
					            'episode': 'Episode 2',
 | 
				
			||||||
 | 
					            'episode_number': 2,
 | 
				
			||||||
 | 
					            'timestamp': 872078400,
 | 
				
			||||||
 | 
					            'upload_date': '19970820',
 | 
				
			||||||
 | 
					            'release_timestamp': 872078400,
 | 
				
			||||||
 | 
					            'release_date': '19970820',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist_count': 4,
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
        'skip': 'Geo-restricted',
 | 
					 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SouthParkDeIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
					class SouthParkDeIE(MTVServicesBaseIE):
 | 
				
			||||||
    IE_NAME = 'southpark.de'
 | 
					    IE_NAME = 'southpark.de'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<url>southpark\.de/(?:(en/(videoclip|collections|episodes|video-clips))|(videoclip|collections|folgen))/(?P<id>(?P<unique_id>.+?)/.+?)(?:\?|#|$))'
 | 
					    _VALID_URL = r'https?://(?:www\.)?southpark\.de/(?:en/)?(?:videoclip|collections|episodes|video-clips|folgen)/(?P<id>[^?#]+)'
 | 
				
			||||||
 | 
					    _GEO_COUNTRIES = ['DE']
 | 
				
			||||||
 | 
					    _GEO_BYPASS = True
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'https://www.southpark.de/videoclip/rsribv/south-park-rueckzug-zum-gummibonbon-wald',
 | 
					        'url': 'https://www.southpark.de/videoclip/rsribv/south-park-rueckzug-zum-gummibonbon-wald',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
@@ -66,123 +102,253 @@ class SouthParkDeIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
				
			|||||||
        # clip
 | 
					        # clip
 | 
				
			||||||
        'url': 'https://www.southpark.de/en/video-clips/ct46op/south-park-tooth-fairy-cartman',
 | 
					        'url': 'https://www.southpark.de/en/video-clips/ct46op/south-park-tooth-fairy-cartman',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'display_id': 'ct46op/south-park-tooth-fairy-cartman',
 | 
				
			||||||
            'title': 'Tooth Fairy Cartman',
 | 
					            'title': 'Tooth Fairy Cartman',
 | 
				
			||||||
            'description': 'md5:db02e23818b4dc9cb5f0c5a7e8833a68',
 | 
					            'description': 'Cartman steals Butters\' tooth and gets four dollars for it.',
 | 
				
			||||||
 | 
					            'duration': 93.26,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 4',
 | 
				
			||||||
 | 
					            'season_number': 4,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 954990360,
 | 
				
			||||||
 | 
					            'upload_date': '20000406',
 | 
				
			||||||
 | 
					            'release_timestamp': 954990360,
 | 
				
			||||||
 | 
					            'release_date': '20000406',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        # episode
 | 
					        # episode
 | 
				
			||||||
        'url': 'https://www.southpark.de/en/episodes/yy0vjs/south-park-the-pandemic-special-season-24-ep-1',
 | 
					        'url': 'https://www.southpark.de/en/episodes/yy0vjs/south-park-the-pandemic-special-season-24-ep-1',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'f5fbd823-04bc-11eb-9b1b-0e40cf2fc285',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'South Park',
 | 
					            'id': '230a4f02-f583-11ea-834d-70df2f866ace',
 | 
				
			||||||
 | 
					            'display_id': 'yy0vjs/south-park-the-pandemic-special-season-24-ep-1',
 | 
				
			||||||
 | 
					            'title': 'The Pandemic Special',
 | 
				
			||||||
            'description': 'md5:ae0d875eff169dcbed16b21531857ac1',
 | 
					            'description': 'md5:ae0d875eff169dcbed16b21531857ac1',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 2724.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 24',
 | 
				
			||||||
 | 
					            'season_number': 24,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 1601932260,
 | 
				
			||||||
 | 
					            'upload_date': '20201005',
 | 
				
			||||||
 | 
					            'release_timestamp': 1601932270,
 | 
				
			||||||
 | 
					            'release_date': '20201005',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        # clip
 | 
					        # clip
 | 
				
			||||||
        'url': 'https://www.southpark.de/videoclip/ct46op/south-park-zahnfee-cartman',
 | 
					        'url': 'https://www.southpark.de/videoclip/ct46op/south-park-zahnfee-cartman',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'display_id': 'ct46op/south-park-zahnfee-cartman',
 | 
				
			||||||
            'title': 'Zahnfee Cartman',
 | 
					            'title': 'Zahnfee Cartman',
 | 
				
			||||||
            'description': 'md5:b917eec991d388811d911fd1377671ac',
 | 
					            'description': 'md5:b917eec991d388811d911fd1377671ac',
 | 
				
			||||||
 | 
					            'duration': 93.26,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 4',
 | 
				
			||||||
 | 
					            'season_number': 4,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 954990360,
 | 
				
			||||||
 | 
					            'upload_date': '20000406',
 | 
				
			||||||
 | 
					            'release_timestamp': 954990360,
 | 
				
			||||||
 | 
					            'release_date': '20000406',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        # episode
 | 
					        # episode
 | 
				
			||||||
        'url': 'https://www.southpark.de/folgen/242csn/south-park-her-mit-dem-hirn-staffel-1-ep-7',
 | 
					        'url': 'https://www.southpark.de/folgen/4r4367/south-park-katerstimmung-staffel-12-ep-3',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'id': '607115f3-496f-40c3-8647-2b0bcff486c0',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'md5:South Park | Pink Eye | E 0107 | HDSS0107X deu | Version: 634312 | Comedy Central S1',
 | 
					            'id': '68c79aa4-ecfd-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'display_id': '4r4367/south-park-katerstimmung-staffel-12-ep-3',
 | 
				
			||||||
 | 
					            'title': 'Katerstimmung',
 | 
				
			||||||
 | 
					            'description': 'md5:94e0e2cd568ffa635e0725518bb4b180',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 1320.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 12',
 | 
				
			||||||
 | 
					            'season_number': 12,
 | 
				
			||||||
 | 
					            'episode': 'Episode 3',
 | 
				
			||||||
 | 
					            'episode_number': 3,
 | 
				
			||||||
 | 
					            'timestamp': 1206504000,
 | 
				
			||||||
 | 
					            'upload_date': '20080326',
 | 
				
			||||||
 | 
					            'release_timestamp': 1206504000,
 | 
				
			||||||
 | 
					            'release_date': '20080326',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_feed_url(self, uri, url=None):
 | 
					 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					 | 
				
			||||||
        config = self._download_json(
 | 
					 | 
				
			||||||
            f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id)
 | 
					 | 
				
			||||||
        return self._remove_template_parameter(config['feedWithQueryParams'])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					class SouthParkLatIE(MTVServicesBaseIE):
 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SouthParkLatIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
					 | 
				
			||||||
    IE_NAME = 'southpark.lat'
 | 
					    IE_NAME = 'southpark.lat'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?southpark\.lat/(?:en/)?(?:video-?clips?|collections|episod(?:e|io)s)/(?P<id>[^/?#&]+)'
 | 
					    _VALID_URL = r'https?://(?:www\.)?southpark\.lat/(?:en/)?(?:video-?clips?|collections|episod(?:e|io)s)/(?P<id>[^?#]+)'
 | 
				
			||||||
 | 
					    _GEO_COUNTRIES = ['MX']
 | 
				
			||||||
 | 
					    _GEO_BYPASS = True
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'https://www.southpark.lat/en/video-clips/ct46op/south-park-tooth-fairy-cartman',
 | 
					        'url': 'https://www.southpark.lat/en/video-clips/ct46op/south-park-tooth-fairy-cartman',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'display_id': 'ct46op/south-park-tooth-fairy-cartman',
 | 
				
			||||||
 | 
					            'title': 'Tooth Fairy Cartman',
 | 
				
			||||||
 | 
					            'description': 'Cartman steals Butters\' tooth and gets four dollars for it.',
 | 
				
			||||||
 | 
					            'duration': 93.26,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 4',
 | 
				
			||||||
 | 
					            'season_number': 4,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 954990360,
 | 
				
			||||||
 | 
					            'upload_date': '20000406',
 | 
				
			||||||
 | 
					            'release_timestamp': 954990360,
 | 
				
			||||||
 | 
					            'release_date': '20000406',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'https://www.southpark.lat/episodios/9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7',
 | 
					        'url': 'https://www.southpark.lat/episodios/9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': '600d273a-ecfd-11e0-aca6-0026b9414f30',
 | 
				
			||||||
 | 
					            'display_id': '9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7',
 | 
				
			||||||
 | 
					            'title': 'Orgía Gatuna ',
 | 
				
			||||||
 | 
					            'description': 'md5:73c6648413f5977026abb792a25c65d5',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 1319.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 3',
 | 
				
			||||||
 | 
					            'season_number': 3,
 | 
				
			||||||
 | 
					            'episode': 'Episode 7',
 | 
				
			||||||
 | 
					            'episode_number': 7,
 | 
				
			||||||
 | 
					            'timestamp': 931924800,
 | 
				
			||||||
 | 
					            'upload_date': '19990714',
 | 
				
			||||||
 | 
					            'release_timestamp': 931924800,
 | 
				
			||||||
 | 
					            'release_date': '19990714',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'https://www.southpark.lat/en/collections/29ve08/south-park-heating-up/lydbrc',
 | 
					        'url': 'https://www.southpark.lat/en/collections/29ve08/south-park-heating-up/lydbrc',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # clip
 | 
					 | 
				
			||||||
        'url': 'https://www.southpark.lat/en/video-clips/ct46op/south-park-tooth-fairy-cartman',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Tooth Fairy Cartman',
 | 
					 | 
				
			||||||
            'description': 'md5:db02e23818b4dc9cb5f0c5a7e8833a68',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # episode
 | 
					 | 
				
			||||||
        'url': 'https://www.southpark.lat/episodios/9h0qbg/south-park-orgia-gatuna-temporada-3-ep-7',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'f5fbd823-04bc-11eb-9b1b-0e40cf2fc285',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'South Park',
 | 
					 | 
				
			||||||
            'description': 'md5:ae0d875eff169dcbed16b21531857ac1',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_url(self, uri, url=None):
 | 
					 | 
				
			||||||
        video_id = self._id_from_uri(uri)
 | 
					 | 
				
			||||||
        config = self._download_json(
 | 
					 | 
				
			||||||
            f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}',
 | 
					 | 
				
			||||||
            video_id)
 | 
					 | 
				
			||||||
        return self._remove_template_parameter(config['feedWithQueryParams'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SouthParkNlIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
					 | 
				
			||||||
    IE_NAME = 'southpark.nl'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<url>southpark\.nl/(?:clips|(?:full-)?episodes|collections)/(?P<id>.+?)(\?|#|$))'
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://www.southpark.nl/feeds/video-player/mrss/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.southpark.nl/full-episodes/s18e06-freemium-isnt-free',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'title': 'Freemium Isn\'t Free',
 | 
					 | 
				
			||||||
            'description': 'Stan is addicted to the new Terrance and Phillip mobile game.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_mincount': 3,
 | 
					 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SouthParkDkIE(SouthParkIE):  # XXX: Do not subclass from concrete IE
 | 
					class SouthParkDkIE(MTVServicesBaseIE):
 | 
				
			||||||
    IE_NAME = 'southparkstudios.dk'
 | 
					    IE_NAME = 'southparkstudios.nu'
 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?(?P<url>southparkstudios\.(?:dk|nu)/(?:clips|full-episodes|collections)/(?P<id>.+?)(\?|#|$))'
 | 
					    _VALID_URL = r'https?://(?:www\.)?southparkstudios\.nu/(?:video-clips|episodes|collections)/(?P<id>[^?#]+)'
 | 
				
			||||||
    _FEED_URL = 'http://www.southparkstudios.dk/feeds/video-player/mrss/'
 | 
					    _GEO_COUNTRIES = ['DK']
 | 
				
			||||||
 | 
					    _GEO_BYPASS = True
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'http://www.southparkstudios.dk/full-episodes/s18e07-grounded-vindaloop',
 | 
					        'url': 'https://www.southparkstudios.nu/episodes/y3uvvc/south-park-grounded-vindaloop-season-18-ep-7',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'f60690a7-21a7-4ee7-8834-d7099a8707ab',
 | 
				
			||||||
 | 
					            'display_id': 'y3uvvc/south-park-grounded-vindaloop-season-18-ep-7',
 | 
				
			||||||
            'title': 'Grounded Vindaloop',
 | 
					            'title': 'Grounded Vindaloop',
 | 
				
			||||||
            'description': 'Butters is convinced he\'s living in a virtual reality.',
 | 
					            'description': 'Butters is convinced he\'s living in a virtual reality.',
 | 
				
			||||||
 | 
					            'channel': 'Comedy Central',
 | 
				
			||||||
 | 
					            'duration': 1319.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 18',
 | 
				
			||||||
 | 
					            'season_number': 18,
 | 
				
			||||||
 | 
					            'episode': 'Episode 7',
 | 
				
			||||||
 | 
					            'episode_number': 7,
 | 
				
			||||||
 | 
					            'timestamp': 1415847600,
 | 
				
			||||||
 | 
					            'upload_date': '20141113',
 | 
				
			||||||
 | 
					            'release_timestamp': 1415768400,
 | 
				
			||||||
 | 
					            'release_date': '20141112',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'playlist_mincount': 3,
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.southparkstudios.dk/collections/2476/superhero-showdown/1',
 | 
					        'url': 'https://www.southparkstudios.nu/collections/8dk7kr/south-park-best-of-south-park/sd5ean',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.southparkstudios.nu/collections/2476/superhero-showdown/1',
 | 
					        'url': 'https://www.southparkstudios.nu/video-clips/k42mrf/south-park-kick-the-baby',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SouthParkComBrIE(MTVServicesBaseIE):
 | 
				
			||||||
 | 
					    IE_NAME = 'southparkstudios.com.br'
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?southparkstudios\.com\.br/(?:en/)?(?:video-clips|episodios|collections|episodes)/(?P<id>[^?#]+)'
 | 
				
			||||||
 | 
					    _GEO_COUNTRIES = ['BR']
 | 
				
			||||||
 | 
					    _GEO_BYPASS = True
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.com.br/video-clips/3vifo0/south-park-welcome-to-mar-a-lago7',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'ccc3e952-7352-11f0-b405-16fff45bc035',
 | 
				
			||||||
 | 
					            'display_id': '3vifo0/south-park-welcome-to-mar-a-lago7',
 | 
				
			||||||
 | 
					            'title': 'Welcome to Mar-a-Lago',
 | 
				
			||||||
 | 
					            'description': 'The President welcomes Mr. Mackey to Mar-a-Lago, a magical place where anything can happen.',
 | 
				
			||||||
 | 
					            'duration': 139.223,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 27',
 | 
				
			||||||
 | 
					            'season_number': 27,
 | 
				
			||||||
 | 
					            'episode': 'Episode 2',
 | 
				
			||||||
 | 
					            'episode_number': 2,
 | 
				
			||||||
 | 
					            'timestamp': 1754546400,
 | 
				
			||||||
 | 
					            'upload_date': '20250807',
 | 
				
			||||||
 | 
					            'release_timestamp': 1754546400,
 | 
				
			||||||
 | 
					            'release_date': '20250807',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.com.br/episodios/940f8z/south-park-cartman-ganha-uma-sonda-anal-temporada-1-ep-1',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.com.br/collections/8dk7kr/south-park-best-of-south-park/sd5ean',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.com.br/en/episodes/5v0oap/south-park-south-park-the-25th-anniversary-concert-ep-1',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SouthParkCoUkIE(MTVServicesBaseIE):
 | 
				
			||||||
 | 
					    IE_NAME = 'southparkstudios.co.uk'
 | 
				
			||||||
 | 
					    _VALID_URL = r'https?://(?:www\.)?southparkstudios\.co\.uk/(?:video-clips|collections|episodes)/(?P<id>[^?#]+)'
 | 
				
			||||||
 | 
					    _GEO_COUNTRIES = ['UK']
 | 
				
			||||||
 | 
					    _GEO_BYPASS = True
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.co.uk/video-clips/8kabfr/south-park-respectclydesauthority',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'id': 'f6d9af23-734e-11f0-b405-16fff45bc035',
 | 
				
			||||||
 | 
					            'display_id': '8kabfr/south-park-respectclydesauthority',
 | 
				
			||||||
 | 
					            'title': '#RespectClydesAuthority',
 | 
				
			||||||
 | 
					            'description': 'After learning about Clyde\'s Podcast, Cartman needs to see it for himself.',
 | 
				
			||||||
 | 
					            'duration': 45.045,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'South Park',
 | 
				
			||||||
 | 
					            'season': 'Season 27',
 | 
				
			||||||
 | 
					            'season_number': 27,
 | 
				
			||||||
 | 
					            'episode': 'Episode 2',
 | 
				
			||||||
 | 
					            'episode_number': 2,
 | 
				
			||||||
 | 
					            'timestamp': 1754546400,
 | 
				
			||||||
 | 
					            'upload_date': '20250807',
 | 
				
			||||||
 | 
					            'release_timestamp': 1754546400,
 | 
				
			||||||
 | 
					            'release_date': '20250807',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.co.uk/episodes/e1yoxn/south-park-imaginationland-season-11-ep-10',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        'url': 'https://www.southparkstudios.co.uk/collections/8dk7kr/south-park-best-of-south-park/sd5ean',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,46 +0,0 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BellatorIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?bellator\.com/[^/]+/[\da-z]{6}(?:[/?#&]|$)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.bellator.com/fight/atwr7k/bellator-158-michael-page-vs-evangelista-cyborg',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'title': 'Michael Page vs. Evangelista Cyborg',
 | 
					 | 
				
			||||||
            'description': 'md5:0d917fc00ffd72dd92814963fc6cbb05',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_count': 3,
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.bellator.com/video-clips/bw6k7n/bellator-158-foundations-michael-venom-page',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://www.bellator.com/feeds/mrss/'
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['US']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ParamountNetworkIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?paramountnetwork\.com/[^/]+/[\da-z]{6}(?:[/?#&]|$)'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        'url': 'http://www.paramountnetwork.com/episodes/j830qm/lip-sync-battle-joel-mchale-vs-jim-rash-season-2-ep-13',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '37ace3a8-1df6-48be-85b8-38df8229e241',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Lip Sync Battle|April 28, 2016|2|209|Joel McHale Vs. Jim Rash|Act 1',
 | 
					 | 
				
			||||||
            'description': 'md5:a739ca8f978a7802f67f8016d27ce114',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            # m3u8 download
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'
 | 
					 | 
				
			||||||
    _GEO_COUNTRIES = ['US']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_feed_query(self, uri):
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            'arcEp': 'paramountnetwork.com',
 | 
					 | 
				
			||||||
            'imageEp': 'paramountnetwork.com',
 | 
					 | 
				
			||||||
            'mgid': uri,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
@@ -1,37 +0,0 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# TODO: Remove - Reason not used anymore - Service moved to youtube
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TVLandIE(MTVServicesInfoExtractor):
 | 
					 | 
				
			||||||
    IE_NAME = 'tvland.com'
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?tvland\.com/(?:video-clips|(?:full-)?episodes)/(?P<id>[^/?#.]+)'
 | 
					 | 
				
			||||||
    _FEED_URL = 'http://www.tvland.com/feeds/mrss/'
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        # Geo-restricted. Without a proxy metadata are still there. With a
 | 
					 | 
				
			||||||
        # proxy it redirects to http://m.tvland.com/app/
 | 
					 | 
				
			||||||
        'url': 'https://www.tvland.com/episodes/s04pzf/everybody-loves-raymond-the-dog-season-1-ep-19',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'description': 'md5:84928e7a8ad6649371fbf5da5e1ad75a',
 | 
					 | 
				
			||||||
            'title': 'The Dog',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_mincount': 5,
 | 
					 | 
				
			||||||
        'skip': '404 Not found',
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'https://www.tvland.com/video-clips/4n87f2/younger-a-first-look-at-younger-season-6',
 | 
					 | 
				
			||||||
        'md5': 'e2c6389401cf485df26c79c247b08713',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '891f7d3c-5b5b-4753-b879-b7ba1a601757',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'Younger|April 30, 2019|6|NO-EPISODE#|A First Look at Younger Season 6',
 | 
					 | 
				
			||||||
            'description': 'md5:595ea74578d3a888ae878dfd1c7d4ab2',
 | 
					 | 
				
			||||||
            'upload_date': '20190430',
 | 
					 | 
				
			||||||
            'timestamp': 1556658000,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        'url': 'http://www.tvland.com/full-episodes/iu0hz6/younger-a-kiss-is-just-a-kiss-season-3-ep-301',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
@@ -1,33 +1,50 @@
 | 
				
			|||||||
from .mtv import MTVServicesInfoExtractor
 | 
					from .mtv import MTVServicesBaseIE
 | 
				
			||||||
 | 
					 | 
				
			||||||
# TODO: Remove - Reason: Outdated Site
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VH1IE(MTVServicesInfoExtractor):
 | 
					class VH1IE(MTVServicesBaseIE):
 | 
				
			||||||
    IE_NAME = 'vh1.com'
 | 
					    IE_NAME = 'vh1.com'
 | 
				
			||||||
    _FEED_URL = 'http://www.vh1.com/feeds/mrss/'
 | 
					    _VALID_URL = r'https?://(?:www\.)?vh1\.com/(?:video-clips|episodes)/(?P<id>[\da-z]{6})'
 | 
				
			||||||
    _TESTS = [{
 | 
					    _TESTS = [{
 | 
				
			||||||
        'url': 'https://www.vh1.com/episodes/0aqivv/nick-cannon-presents-wild-n-out-foushee-season-16-ep-12',
 | 
					        'url': 'https://www.vh1.com/episodes/d06ta1/barely-famous-barely-famous-season-1-ep-1',
 | 
				
			||||||
        'info_dict': {
 | 
					        'info_dict': {
 | 
				
			||||||
            'title': 'Fousheé',
 | 
					            'id': '4af4cf2c-a854-11e4-9596-0026b9414f30',
 | 
				
			||||||
            'description': 'Fousheé joins Team Evolutions fight against Nick and Team Revolution in Baby Daddy, Baby Mama; Kick Em Out the Classroom; Backseat of My Ride and Wildstyle; and Fousheé performs.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'playlist_mincount': 4,
 | 
					 | 
				
			||||||
        'skip': '404 Not found',
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # Clip
 | 
					 | 
				
			||||||
        'url': 'https://www.vh1.com/video-clips/e0sja0/nick-cannon-presents-wild-n-out-foushee-clap-for-him',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': 'a07563f7-a37b-4e7f-af68-85855c2c7cc3',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					            'ext': 'mp4',
 | 
				
			||||||
            'title': 'Fousheé - "clap for him"',
 | 
					            'display_id': 'd06ta1',
 | 
				
			||||||
            'description': 'Singer Fousheé hits the Wild N Out: In the Dark stage with a performance of the tongue-in-cheek track "clap for him" from her 2021 album "time machine."',
 | 
					            'title': 'Barely Famous',
 | 
				
			||||||
            'upload_date': '20210826',
 | 
					            'description': 'md5:6da5c9d88012eba0a80fc731c99b5fed',
 | 
				
			||||||
 | 
					            'channel': 'VH1',
 | 
				
			||||||
 | 
					            'duration': 1280.0,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'Barely Famous',
 | 
				
			||||||
 | 
					            'season': 'Season 1',
 | 
				
			||||||
 | 
					            'season_number': 1,
 | 
				
			||||||
 | 
					            'episode': 'Episode 1',
 | 
				
			||||||
 | 
					            'episode_number': 1,
 | 
				
			||||||
 | 
					            'timestamp': 1426680000,
 | 
				
			||||||
 | 
					            'upload_date': '20150318',
 | 
				
			||||||
 | 
					            'release_timestamp': 1426680000,
 | 
				
			||||||
 | 
					            'release_date': '20150318',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'params': {
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
            # m3u8 download
 | 
					    }, {
 | 
				
			||||||
            'skip_download': True,
 | 
					        'url': 'https://www.vh1.com/video-clips/ryzt2n/love-hip-hop-miami-love-hip-hop-miami-season-5-recap',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '59e62974-4a5c-4417-91c3-5044cb2f4ce2',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'display_id': 'ryzt2n',
 | 
				
			||||||
 | 
					            'title': 'Love & Hip Hop Miami - Season 5 Recap',
 | 
				
			||||||
 | 
					            'description': 'md5:4e49c65d0007bfc8d06db555a6b76ef0',
 | 
				
			||||||
 | 
					            'duration': 792.083,
 | 
				
			||||||
 | 
					            'thumbnail': r're:https://images\.paramount\.tech/uri/mgid:arc:imageassetref:',
 | 
				
			||||||
 | 
					            'series': 'Love & Hip Hop Miami',
 | 
				
			||||||
 | 
					            'season': 'Season 6',
 | 
				
			||||||
 | 
					            'season_number': 6,
 | 
				
			||||||
 | 
					            'episode': 'Episode 0',
 | 
				
			||||||
 | 
					            'episode_number': 0,
 | 
				
			||||||
 | 
					            'timestamp': 1732597200,
 | 
				
			||||||
 | 
					            'upload_date': '20241126',
 | 
				
			||||||
 | 
					            'release_timestamp': 1732597200,
 | 
				
			||||||
 | 
					            'release_date': '20241126',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        'params': {'skip_download': 'm3u8'},
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
 | 
					 | 
				
			||||||
    _VALID_URL = r'https?://(?:www\.)?vh1\.com/(?:video-clips|episodes)/(?P<id>[^/?#.]+)'
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user