code:
# coding: utf-8
from __future__ import unicode_literals

import functools
import json
import re

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    age_restricted,
    clean_html,
    ExtractorError,
    int_or_none,
    OnDemandPagedList,
    try_get,
    unescapeHTML,
    urlencode_postdata,
)

class DailymotionBaseInfoExtractor(InfoExtractor):
    _FAMILY_FILTER = None
    _HEADERS = {
        'Content-Type': 'application/json',
        'Origin': 'https://www.dailymotion.com',
    }
    _NETRC_MACHINE = 'dailymotion'

    def _get_dailymotion_cookies(self):
        return self._get_cookies('https://www.dailymotion.com/')

    @staticmethod
    def _get_cookie_value(cookies, name):
        cookie = cookies.get(name)
        if cookie:
            return cookie.value

    def _set_dailymotion_cookie(self, name, value):
        self._set_cookie('www.dailymotion.com', name, value)

    def _real_initialize(self):
        cookies = self._get_dailymotion_cookies()
        ff = self._get_cookie_value(cookies, 'ff')
        self._FAMILY_FILTER = ff == 'on' if ff else age_restricted(18, self._downloader.params.get('age_limit'))
        self._set_dailymotion_cookie('ff', 'on' if self._FAMILY_FILTER else 'off')

    def _call_api(self, object_type, xid, object_fields, note, filter_extra=None):
        if not self._HEADERS.get('Authorization'):
            cookies = self._get_dailymotion_cookies()
            token = self._get_cookie_value(cookies, 'access_token') or self._get_cookie_value(cookies, 'client_token')
            if not token:
                data = {
                    'client_id': 'f1a362d288c1b98099c7',
                    'client_secret': 'eea605b96e01c796ff369935357eca920c5da4c5',
                }
                username, password = self._get_login_info()
                if username:
                    data.update({
                        'grant_type': 'password',
                        'password': password,
                        'username': username,
                    })
                else:
                    data['grant_type'] = 'client_credentials'
                try:
                    token = self._download_json(
                        'https://graphql.api.dailymotion.com/oauth/token',
                        None, 'Downloading Access Token',
                        data=urlencode_postdata(data))['access_token']
                except ExtractorError as e:
                    if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
                        raise ExtractorError(self._parse_json(
                            e.cause.read().decode(), xid)['error_description'], expected=True)
                    raise
                self._set_dailymotion_cookie('access_token' if username else 'client_token', token)
            self._HEADERS['Authorization'] = 'Bearer ' + token

        resp = self._download_json(
            'https://graphql.api.dailymotion.com/', xid, note, data=json.dumps({
                'query': '''{
  %s(xid: "%s"%s) {
    %s
  }
}''' % (object_type, xid, ', ' + filter_extra if filter_extra else '', object_fields),
            }).encode(), headers=self._HEADERS)
        obj = resp['data'][object_type]
        if not obj:
            raise ExtractorError(resp['errors'][0]['message'], expected=True)
        return obj

class DailymotionIE(DailymotionBaseInfoExtractor):
    _VALID_URL = r'''(?ix)
                    https?://
                        (?:
                            (?:(?:www|touch)\.)?dailymotion\.[a-z]{2,3}/(?:(?:(?:embed|swf|\#)/)?video|swf)|
                            (?:www\.)?lequipe\.fr/video
                        )
                        /(?P<id>[^/?_]+)(?:.+?\bplaylist=(?P<playlist_id>x[0-9a-z]+))?
                    '''
    IE_NAME = 'dailymotion'
    _TESTS = [{
        'url': 'http://www.dailymotion.com/video/x5kesuj_office-christmas-party-review-jason-bateman-olivia-munn-t-j-miller_news',
        'md5': '074b95bdee76b9e3654137aee9c79dfe',
        'info_dict': {
            'id': 'x5kesuj',
            'ext': 'mp4',
            'title': 'Office Christmas Party Review –  Jason Bateman, Olivia Munn, T.J. Miller',
            'description': 'Office Christmas Party Review -  Jason Bateman, Olivia Munn, T.J. Miller',
            'duration': 187,
            'timestamp': 1493651285,
            'upload_date': '20170501',
            'uploader': 'Deadline',
            'uploader_id': 'x1xm8ri',
            'age_limit': 0,
        },
    }, {
        'url': 'https://www.dailymotion.com/video/x2iuewm_steam-machine-models-pricing-listed-on-steam-store-ign-news_videogames',
        'md5': '2137c41a8e78554bb09225b8eb322406',
        'info_dict': {
            'id': 'x2iuewm',
            'ext': 'mp4',
            'title': 'Steam Machine Models, Pricing Listed on Steam Store - IGN News',
            'description': 'Several come bundled with the Steam Controller.',
            'thumbnail': r're:^https?:.*\.(?:jpg|png)$',
            'duration': 74,
            'timestamp': 1425657362,
            'upload_date': '20150306',
            'uploader': 'IGN',
            'uploader_id': 'xijv66',
            'age_limit': 0,
            'view_count': int,
        },
        'skip': 'video gone',
    }, {
        # Vevo video
        'url': 'http://www.dailymotion.com/video/x149uew_katy-perry-roar-official_musi',
        'info_dict': {
            'title': 'Roar (Official)',
            'id': 'USUV71301934',
            'ext': 'mp4',
            'uploader': 'Katy Perry',
            'upload_date': '20130905',
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'VEVO is only available in some countries',
    }, {
        # age-restricted video
        'url': 'http://www.dailymotion.com/video/xyh2zz_leanna-decker-cyber-girl-of-the-year-desires-nude-playboy-plus_redband',
        'md5': '0d667a7b9cebecc3c89ee93099c4159d',
        'info_dict': {
            'id': 'xyh2zz',
            'ext': 'mp4',
            'title': 'Leanna Decker - Cyber Girl Of The Year Desires Nude [Playboy Plus]',
            'uploader': 'HotWaves1012',
            'age_limit': 18,
        },
        'skip': 'video gone',
    }, {
        # geo-restricted, player v5
        'url': 'http://www.dailymotion.com/video/xhza0o',
        'only_matching': True,
    }, {
        # with subtitles
        'url': 'http://www.dailymotion.com/video/x20su5f_the-power-of-nightmares-1-the-rise-of-the-politics-of-fear-bbc-2004_news',
        'only_matching': True,
    }, {
        'url': 'http://www.dailymotion.com/swf/video/x3n92nf',
        'only_matching': True,
    }, {
        'url': 'http://www.dailymotion.com/swf/x3ss1m_funny-magic-trick-barry-and-stuart_fun',
        'only_matching': True,
    }, {
        'url': 'https://www.lequipe.fr/video/x791mem',
        'only_matching': True,
    }, {
        'url': 'https://www.lequipe.fr/video/k7MtHciueyTcrFtFKA2',
        'only_matching': True,
    }, {
        'url': 'https://www.dailymotion.com/video/x3z49k?playlist=xv4bw',
        'only_matching': True,
    }]
    _GEO_BYPASS = False
    _COMMON_MEDIA_FIELDS = '''description
      geoblockedCountries {
        allowed
      }
      xid'''

    @staticmethod
    def _extract_urls(webpage):
        urls = []
        # Look for embedded Dailymotion player
        # https://developer.dailymotion.com/player#player-parameters
        for mobj in re.finditer(
                r'<(?:(?:embed|iframe)[^>]+?src=|input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=)(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage):
            urls.append(unescapeHTML(mobj.group('url')))
        for mobj in re.finditer(
                r'(?s)DM\.player\([^,]+,\s*{.*?video[\'"]?\s*:\s*["\']?(?P<id>[0-9a-zA-Z]+).+?}\s*\);', webpage):
            urls.append('https://www.dailymotion.com/embed/video/' + mobj.group('id'))
        return urls

    def _real_extract(self, url):
        video_id, playlist_id = re.match(self._VALID_URL, url).groups()

        if playlist_id:
            if not self._downloader.params.get('noplaylist'):
                self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
                return self.url_result(
                    'http://www.dailymotion.com/playlist/' + playlist_id,
                    'DailymotionPlaylist', playlist_id)
            self.to_screen('Downloading just video %s because of --no-playlist' % video_id)

        password = self._downloader.params.get('videopassword')
        media = self._call_api(
            'media', video_id, '''... on Video {
      %s
      stats {
        likes {
          total
        }
        views {
          total
        }
      }
    }
    ... on Live {
      %s
      audienceCount
      isOnAir
    }''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata',
            'password: "%s"' % self._downloader.params.get('videopassword') if password else None)
        xid = media['xid']

        metadata = self._download_json(
            'https://www.dailymotion.com/player/metadata/video/' + xid,
            xid, 'Downloading metadata JSON',
            query={'app': 'com.dailymotion.neon'})

        error = metadata.get('error')
        if error:
            title = error.get('title') or error['raw_message']
            # See https://developer.dailymotion.com/api#access-error
            if error.get('code') == 'DM007':
                allowed_countries = try_get(media, lambda x: x['geoblockedCountries']['allowed'], list)
                self.raise_geo_restricted(msg=title, countries=allowed_countries)
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, title), expected=True)

        title = metadata['title']
        is_live = media.get('isOnAir')
        formats = []
        for quality, media_list in metadata['qualities'].items():
            for m in media_list:
                media_url = m.get('url')
                media_type = m.get('type')
                if not media_url or media_type == 'application/vnd.lumberjack.manifest':
                    continue
                if media_type == 'application/x-mpegURL':
                    formats.extend(self._extract_m3u8_formats(
                        media_url, video_id, 'mp4',
                        'm3u8' if is_live else 'm3u8_native',
                        m3u8_id='hls', fatal=False))
                else:
                    f = {
                        'url': media_url,
                        'format_id': 'http-' + quality,
                    }
                    m = re.search(r'/H264-(\d+)x(\d+)(?:-(60)/)?', media_url)
                    if m:
                        width, height, fps = map(int_or_none, m.groups())
                        f.update({
                            'fps': fps,
                            'height': height,
                            'width': width,
                        })
                    formats.append(f)
        for f in formats:
            f['url'] = f['url'].split('#')[0]
            if not f.get('fps') and f['format_id'].endswith('@60'):
                f['fps'] = 60
        self._sort_formats(formats)

        subtitles = {}
        subtitles_data = try_get(metadata, lambda x: x['subtitles']['data'], dict) or {}
        for subtitle_lang, subtitle in subtitles_data.items():
            subtitles[subtitle_lang] = [{
                'url': subtitle_url,
            } for subtitle_url in subtitle.get('urls', [])]

        thumbnails = []
        for height, poster_url in metadata.get('posters', {}).items():
            thumbnails.append({
                'height': int_or_none(height),
                'id': height,
                'url': poster_url,
            })

        owner = metadata.get('owner') or {}
        stats = media.get('stats') or {}
        get_count = lambda x: int_or_none(try_get(stats, lambda y: y[x + 's']['total']))

        return {
            'id': video_id,
            'title': self._live_title(title) if is_live else title,
            'description': clean_html(media.get('description')),
            'thumbnails': thumbnails,
            'duration': int_or_none(metadata.get('duration')) or None,
            'timestamp': int_or_none(metadata.get('created_time')),
            'uploader': owner.get('screenname'),
            'uploader_id': owner.get('id') or metadata.get('screenname'),
            'age_limit': 18 if metadata.get('explicit') else 0,
            'tags': metadata.get('tags'),
            'view_count': get_count('view') or int_or_none(media.get('audienceCount')),
            'like_count': get_count('like'),
            'formats': formats,
            'subtitles': subtitles,
            'is_live': is_live,
        }

class DailymotionPlaylistBaseIE(DailymotionBaseInfoExtractor):
    _PAGE_SIZE = 100

    def _fetch_page(self, playlist_id, page):
        page += 1
        videos = self._call_api(
            self._OBJECT_TYPE, playlist_id,
            '''videos(allowExplicit: %s, first: %d, page: %d) {
      edges {
        node {
          xid
          url
        }
      }
    }''' % ('false' if self._FAMILY_FILTER else 'true', self._PAGE_SIZE, page),
            'Downloading page %d' % page)['videos']
        for edge in videos['edges']:
            node = edge['node']
            yield self.url_result(
                node['url'], DailymotionIE.ie_key(), node['xid'])

    def _real_extract(self, url):
        playlist_id = self._match_id(url)
        entries = OnDemandPagedList(functools.partial(
            self._fetch_page, playlist_id), self._PAGE_SIZE)
        return self.playlist_result(
            entries, playlist_id)

class DailymotionPlaylistIE(DailymotionPlaylistBaseIE):
    IE_NAME = 'dailymotion:playlist'
    _VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/playlist/(?P<id>x[0-9a-z]+)'
    _TESTS = [{
        'url': 'http://www.dailymotion.com/playlist/xv4bw_nqtv_sport/1#video=xl8v3q',
        'info_dict': {
            'id': 'xv4bw',
        },
        'playlist_mincount': 20,
    }]
    _OBJECT_TYPE = 'collection'

class DailymotionUserIE(DailymotionPlaylistBaseIE):
    IE_NAME = 'dailymotion:user'
    _VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/(?!(?:embed|swf|#|video|playlist)/)(?:(?:old/)?user/)?(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://www.dailymotion.com/user/nqtv',
        'info_dict': {
            'id': 'nqtv',
        },
        'playlist_mincount': 152,
    }, {
        'url': 'http://www.dailymotion.com/user/UnderProject',
        'info_dict': {
            'id': 'UnderProject',
        },
        'playlist_mincount': 1000,
        'skip': 'Takes too long time',
    }, {
        'url': 'https://www.dailymotion.com/user/nqtv',
        'info_dict': {
            'id': 'nqtv',
        },
        'playlist_mincount': 148,
        'params': {
            'age_limit': 0,
        },
    }]
    _OBJECT_TYPE = 'channel'


code:
# coding: utf-8

from __future__ import unicode_literals

import itertools

from .common import InfoExtractor
from ..compat import (
    compat_parse_qs,
    compat_urllib_parse_unquote,
    compat_urlparse,
)

class DaumBaseIE(InfoExtractor):
    _KAKAO_EMBED_BASE = 'http://tv.kakao.com/embed/player/cliplink/'

class DaumIE(DaumBaseIE):
    _VALID_URL = r'https?://(?:(?:m\.)?tvpot\.daum\.net/v/|videofarm\.daum\.net/controller/player/VodPlayer\.swf\?vid=)(?P<id>[^?#&]+)'
    IE_NAME = 'daum.net'

    _TESTS = [{
        'url': 'http://tvpot.daum.net/v/vab4dyeDBysyBssyukBUjBz',
        'info_dict': {
            'id': 'vab4dyeDBysyBssyukBUjBz',
            'ext': 'mp4',
            'title': '마크 헌트 vs 안토니오 실바',
            'description': 'Mark Hunt vs Antonio Silva',
            'upload_date': '20131217',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'duration': 2117,
            'view_count': int,
            'comment_count': int,
            'uploader_id': 186139,
            'uploader': '콘간지',
            'timestamp': 1387310323,
        },
    }, {
        'url': 'http://m.tvpot.daum.net/v/65139429',
        'info_dict': {
            'id': '65139429',
            'ext': 'mp4',
            'title': '1297회, \'아빠 아들로 태어나길 잘 했어\' 민수, 감동의 눈물[아빠 어디가] 20150118',
            'description': 'md5:79794514261164ff27e36a21ad229fc5',
            'upload_date': '20150118',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'duration': 154,
            'view_count': int,
            'comment_count': int,
            'uploader': 'MBC 예능',
            'uploader_id': 132251,
            'timestamp': 1421604228,
        },
    }, {
        'url': 'http://tvpot.daum.net/v/07dXWRka62Y%24',
        'only_matching': True,
    }, {
        'url': 'http://videofarm.daum.net/controller/player/VodPlayer.swf?vid=vwIpVpCQsT8%24&ref=',
        'info_dict': {
            'id': 'vwIpVpCQsT8$',
            'ext': 'flv',
            'title': '01-Korean War ( Trouble on the horizon )',
            'description': 'Korean War 01\r\nTrouble on the horizon\r\n전쟁의 먹구름',
            'upload_date': '20080223',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'duration': 249,
            'view_count': int,
            'comment_count': int,
            'uploader': '까칠한 墮落始祖 황비홍님의',
            'uploader_id': 560824,
            'timestamp': 1203770745,
        },
    }, {
        # Requires dte_type=WEB (#9972)
        'url': 'http://tvpot.daum.net/v/s3794Uf1NZeZ1qMpGpeqeRU',
        'md5': 'a8917742069a4dd442516b86e7d66529',
        'info_dict': {
            'id': 's3794Uf1NZeZ1qMpGpeqeRU',
            'ext': 'mp4',
            'title': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)',
            'description': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)\r\n\r\n[쇼! 음악중심] 20160611, 507회',
            'upload_date': '20170129',
            'uploader': '쇼! 음악중심',
            'uploader_id': 2653210,
            'timestamp': 1485684628,
        },
    }]

    def _real_extract(self, url):
        video_id = compat_urllib_parse_unquote(self._match_id(url))
        if not video_id.isdigit():
            video_id += '@my'
        return self.url_result(
            self._KAKAO_EMBED_BASE + video_id, 'Kakao', video_id)

class DaumClipIE(DaumBaseIE):
    _VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:clip/ClipView.(?:do|tv)|mypot/View.do)\?.*?clipid=(?P<id>\d+)'
    IE_NAME = 'daum.net:clip'
    _URL_TEMPLATE = 'http://tvpot.daum.net/clip/ClipView.do?clipid=%s'

    _TESTS = [{
        'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
        'info_dict': {
            'id': '52554690',
            'ext': 'mp4',
            'title': 'DOTA 2GETHER 시즌2 6회 - 2부',
            'description': 'DOTA 2GETHER 시즌2 6회 - 2부',
            'upload_date': '20130831',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'duration': 3868,
            'view_count': int,
            'uploader': 'GOMeXP',
            'uploader_id': 6667,
            'timestamp': 1377911092,
        },
    }, {
        'url': 'http://m.tvpot.daum.net/clip/ClipView.tv?clipid=54999425',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return False if DaumPlaylistIE.suitable(url) or DaumUserIE.suitable(url) else super(DaumClipIE, cls).suitable(url)

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.url_result(
            self._KAKAO_EMBED_BASE + video_id, 'Kakao', video_id)

class DaumListIE(InfoExtractor):
    def _get_entries(self, list_id, list_id_type):
        name = None
        entries = []
        for pagenum in itertools.count(1):
            list_info = self._download_json(
                'http://tvpot.daum.net/mypot/json/GetClipInfo.do?size=48&init=true&order=date&page=%d&%s=%s' % (
                    pagenum, list_id_type, list_id), list_id, 'Downloading list info - %s' % pagenum)

            entries.extend([
                self.url_result(
                    'http://tvpot.daum.net/v/%s' % clip['vid'])
                for clip in list_info['clip_list']
            ])

            if not name:
                name = list_info.get('playlist_bean', {}).get('name') or \
                    list_info.get('potInfo', {}).get('name')

            if not list_info.get('has_more'):
                break

        return name, entries

    def _check_clip(self, url, list_id):
        query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
        if 'clipid' in query_dict:
            clip_id = query_dict['clipid'][0]
            if self._downloader.params.get('noplaylist'):
                self.to_screen('Downloading just video %s because of --no-playlist' % clip_id)
                return self.url_result(DaumClipIE._URL_TEMPLATE % clip_id, 'DaumClip')
            else:
                self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % list_id)

class DaumPlaylistIE(DaumListIE):
    _VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/mypot/(?:View\.do|Top\.tv)\?.*?playlistid=(?P<id>[0-9]+)'
    IE_NAME = 'daum.net:playlist'
    _URL_TEMPLATE = 'http://tvpot.daum.net/mypot/View.do?playlistid=%s'

    _TESTS = [{
        'note': 'Playlist url with clipid',
        'url': 'http://tvpot.daum.net/mypot/View.do?playlistid=6213966&clipid=73806844',
        'info_dict': {
            'id': '6213966',
            'title': 'Woorissica Official',
        },
        'playlist_mincount': 181
    }, {
        'note': 'Playlist url with clipid - noplaylist',
        'url': 'http://tvpot.daum.net/mypot/View.do?playlistid=6213966&clipid=73806844',
        'info_dict': {
            'id': '73806844',
            'ext': 'mp4',
            'title': '151017 Airport',
            'upload_date': '20160117',
        },
        'params': {
            'noplaylist': True,
            'skip_download': True,
        }
    }]

    @classmethod
    def suitable(cls, url):
        return False if DaumUserIE.suitable(url) else super(DaumPlaylistIE, cls).suitable(url)

    def _real_extract(self, url):
        list_id = self._match_id(url)

        clip_result = self._check_clip(url, list_id)
        if clip_result:
            return clip_result

        name, entries = self._get_entries(list_id, 'playlistid')

        return self.playlist_result(entries, list_id, name)

class DaumUserIE(DaumListIE):
    _VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/mypot/(?:View|Top)\.(?:do|tv)\?.*?ownerid=(?P<id>[0-9a-zA-Z]+)'
    IE_NAME = 'daum.net:user'

    _TESTS = [{
        'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0',
        'info_dict': {
            'id': 'o2scDLIVbHc0',
            'title': '마이 리틀 텔레비전',
        },
        'playlist_mincount': 213
    }, {
        'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&clipid=73801156',
        'info_dict': {
            'id': '73801156',
            'ext': 'mp4',
            'title': '[미공개] 김구라, 오만석이 부릅니다 \'오케피\' - 마이 리틀 텔레비전 20160116',
            'upload_date': '20160117',
            'description': 'md5:5e91d2d6747f53575badd24bd62b9f36'
        },
        'params': {
            'noplaylist': True,
            'skip_download': True,
        }
    }, {
        'note': 'Playlist url has ownerid and playlistid, playlistid takes precedence',
        'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&playlistid=6196631',
        'info_dict': {
            'id': '6196631',
            'title': '마이 리틀 텔레비전 - 20160109',
        },
        'playlist_count': 11
    }, {
        'url': 'http://tvpot.daum.net/mypot/Top.do?ownerid=o2scDLIVbHc0',
        'only_matching': True,
    }, {
        'url': 'http://m.tvpot.daum.net/mypot/Top.tv?ownerid=45x1okb1If50&playlistid=3569733',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        list_id = self._match_id(url)

        clip_result = self._check_clip(url, list_id)
        if clip_result:
            return clip_result

        query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
        if 'playlistid' in query_dict:
            playlist_id = query_dict['playlistid'][0]
            return self.url_result(DaumPlaylistIE._URL_TEMPLATE % playlist_id, 'DaumPlaylist')

        name, entries = self._get_entries(list_id, 'ownerid')

        return self.playlist_result(entries, list_id, name)

repo_name: rrosajp/youtube


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor

class DBTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?dagbladet\.no/video/(?:(?:embed|(?P<display_id>[^/]+))/)?(?P<id>[0-9A-Za-z_-]{11}|[a-zA-Z0-9]{8})'
    _TESTS = [{
        'url': 'https://www.dagbladet.no/video/PynxJnNWChE/',
        'md5': 'b8f850ba1860adbda668d367f9b77699',
        'info_dict': {
            'id': 'PynxJnNWChE',
            'ext': 'mp4',
            'title': 'Skulle teste ut fornøyelsespark, men kollegaen var bare opptatt av bikinikroppen',
            'description': 'md5:49cc8370e7d66e8a2ef15c3b4631fd3f',
            'thumbnail': r're:https?://.*\.jpg',
            'upload_date': '20160916',
            'duration': 69,
            'uploader_id': 'UCk5pvsyZJoYJBd7_oFPTlRQ',
            'uploader': 'Dagbladet',
        },
        'add_ie': ['Youtube']
    }, {
        'url': 'https://www.dagbladet.no/video/embed/xlGmyIeN9Jo/?autoplay=false',
        'only_matching': True,
    }, {
        'url': 'https://www.dagbladet.no/video/truer-iran-bor-passe-dere/PalfB2Cw',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return [url for _, url in re.findall(
            r'<iframe[^>]+src=(["\'])((?:https?:)?//(?:www\.)?dagbladet\.no/video/embed/(?:[0-9A-Za-z_-]{11}|[a-zA-Z0-9]{8}).*?)\1',
            webpage)]

    def _real_extract(self, url):
        display_id, video_id = re.match(self._VALID_URL, url).groups()
        info = {
            '_type': 'url_transparent',
            'id': video_id,
            'display_id': display_id,
        }
        if len(video_id) == 11:
            info.update({
                'url': video_id,
                'ie_key': 'Youtube',
            })
        else:
            info.update({
                'url': 'jwplatform:' + video_id,
                'ie_key': 'JWPlatform',
            })
        return info


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    float_or_none,
    int_or_none,
    unified_timestamp,
    url_or_none,
)

class DctpTvIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?dctp\.tv/(?:#/)?filme/(?P<id>[^/?#&]+)'
    _TESTS = [{
        # 4x3
        'url': 'http://www.dctp.tv/filme/videoinstallation-fuer-eine-kaufhausfassade/',
        'md5': '3ffbd1556c3fe210724d7088fad723e3',
        'info_dict': {
            'id': '95eaa4f33dad413aa17b4ee613cccc6c',
            'display_id': 'videoinstallation-fuer-eine-kaufhausfassade',
            'ext': 'm4v',
            'title': 'Videoinstallation für eine Kaufhausfassade',
            'description': 'Kurzfilm',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 71.24,
            'timestamp': 1302172322,
            'upload_date': '20110407',
        },
    }, {
        # 16x9
        'url': 'http://www.dctp.tv/filme/sind-youtuber-die-besseren-lehrer/',
        'only_matching': True,
    }]

    _BASE_URL = 'http://dctp-ivms2-restapi.s3.amazonaws.com'

    def _real_extract(self, url):
        display_id = self._match_id(url)

        version = self._download_json(
            '%s/version.json' % self._BASE_URL, display_id,
            'Downloading version JSON')

        restapi_base = '%s/%s/restapi' % (
            self._BASE_URL, version['version_name'])

        info = self._download_json(
            '%s/slugs/%s.json' % (restapi_base, display_id), display_id,
            'Downloading video info JSON')

        media = self._download_json(
            '%s/media/%s.json' % (restapi_base, compat_str(info['object_id'])),
            display_id, 'Downloading media JSON')

        uuid = media['uuid']
        title = media['title']
        is_wide = media.get('is_wide')
        formats = []

        def add_formats(suffix):
            templ = 'https://%%s/%s_dctp_%s.m4v' % (uuid, suffix)
            formats.extend([{
                'format_id': 'hls-' + suffix,
                'url': templ % 'cdn-segments.dctp.tv' + '/playlist.m3u8',
                'protocol': 'm3u8_native',
            }, {
                'format_id': 's3-' + suffix,
                'url': templ % 'completed-media.s3.amazonaws.com',
            }, {
                'format_id': 'http-' + suffix,
                'url': templ % 'cdn-media.dctp.tv',
            }])

        add_formats('0500_' + ('16x9' if is_wide else '4x3'))
        if is_wide:
            add_formats('720p')

        thumbnails = []
        images = media.get('images')
        if isinstance(images, list):
            for image in images:
                if not isinstance(image, dict):
                    continue
                image_url = url_or_none(image.get('url'))
                if not image_url:
                    continue
                thumbnails.append({
                    'url': image_url,
                    'width': int_or_none(image.get('width')),
                    'height': int_or_none(image.get('height')),
                })

        return {
            'id': uuid,
            'display_id': display_id,
            'title': title,
            'alt_title': media.get('subtitle'),
            'description': media.get('description') or media.get('teaser'),
            'timestamp': unified_timestamp(media.get('created')),
            'duration': float_or_none(media.get('duration_in_ms'), scale=1000),
            'thumbnails': thumbnails,
            'formats': formats,
        }


code:
from __future__ import unicode_literals

import json
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    orderedSet,
)

class DeezerPlaylistIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?deezer\.com/playlist/(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://www.deezer.com/playlist/176747451',
        'info_dict': {
            'id': '176747451',
            'title': 'Best!',
            'uploader': 'Anonymous',
            'thumbnail': r're:^https?://cdn-images\.deezer\.com/images/cover/.*\.jpg$',
        },
        'playlist_count': 30,
        'skip': 'Only available in .de',
    }

    def _real_extract(self, url):
        if 'test' not in self._downloader.params:
            self._downloader.report_warning('For now, this extractor only supports the 30 second previews. Patches welcome!')

        mobj = re.match(self._VALID_URL, url)
        playlist_id = mobj.group('id')

        webpage = self._download_webpage(url, playlist_id)
        geoblocking_msg = self._html_search_regex(
            r'
(.*?)
', webpage, 'geoblocking message',
            default=None)
        if geoblocking_msg is not None:
            raise ExtractorError(
                'Deezer said: %s' % geoblocking_msg, expected=True)

        data_json = self._search_regex(
            (r'__DZR_APP_STATE__\s*=\s*({.+?})\s*</script>',
             r'naboo\.display\(\'[^\']+\',\s*(.*?)\);\n'),
            webpage, 'data JSON')
        data = json.loads(data_json)

        playlist_title = data.get('DATA', {}).get('TITLE')
        playlist_uploader = data.get('DATA', {}).get('PARENT_USERNAME')
        playlist_thumbnail = self._search_regex(
            r'<img id="naboo_playlist_image".*?src="([^"]+)"', webpage,
            'playlist thumbnail')

        preview_pattern = self._search_regex(
            r"var SOUND_PREVIEW_GATEWAY\s*=\s*'([^']+)';", webpage,
            'preview URL pattern', fatal=False)
        entries = []
        for s in data['SONGS']['data']:
            puid = s['MD5_ORIGIN']
            preview_video_url = preview_pattern.\
                replace('{0}', puid[0]).\
                replace('{1}', puid).\
                replace('{2}', s['MEDIA_VERSION'])
            formats = [{
                'format_id': 'preview',
                'url': preview_video_url,
                'preference': -100,  # Only the first 30 seconds
                'ext': 'mp3',
            }]
            self._sort_formats(formats)
            artists = ', '.join(
                orderedSet(a['ART_NAME'] for a in s['ARTISTS']))
            entries.append({
                'id': s['SNG_ID'],
                'duration': int_or_none(s.get('DURATION')),
                'title': '%s - %s' % (artists, s['SNG_TITLE']),
                'uploader': s['ART_NAME'],
                'uploader_id': s['ART_ID'],
                'age_limit': 16 if s.get('EXPLICIT_LYRICS') == '1' else 0,
                'formats': formats,
            })

        return {
            '_type': 'playlist',
            'id': playlist_id,
            'title': playlist_title,
            'uploader': playlist_uploader,
            'thumbnail': playlist_thumbnail,
            'entries': entries,
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor

class DefenseGouvFrIE(InfoExtractor):
    IE_NAME = 'defense.gouv.fr'
    _VALID_URL = r'https?://.*?\.defense\.gouv\.fr/layout/set/ligthboxvideo/base-de-medias/webtv/(?P<id>[^/?#]*)'

    _TEST = {
        'url': 'http://www.defense.gouv.fr/layout/set/ligthboxvideo/base-de-medias/webtv/attaque-chimique-syrienne-du-21-aout-2013-1',
        'md5': '75bba6124da7e63d2d60b5244ec9430c',
        'info_dict': {
            'id': '11213',
            'ext': 'mp4',
            'title': 'attaque-chimique-syrienne-du-21-aout-2013-1'
        }
    }

    def _real_extract(self, url):
        title = self._match_id(url)
        webpage = self._download_webpage(url, title)

        video_id = self._search_regex(
            r"flashvars.pvg_id=\"(\d+)\";",
            webpage, 'ID')

        json_url = (
            'http://static.videos.gouv.fr/brightcovehub/export/json/%s' %
            video_id)
        info = self._download_json(json_url, title, 'Downloading JSON config')
        video_url = info['renditions'][0]['url']

        return {
            'id': video_id,
            'ext': 'mp4',
            'url': video_url,
            'title': title,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

import re
import os.path

from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    url_basename,
    remove_start,
)

class DemocracynowIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?democracynow\.org/(?P<id>[^\?]*)'
    IE_NAME = 'democracynow'
    _TESTS = [{
        'url': 'http://www.democracynow.org/shows/2015/7/3',
        'md5': '3757c182d3d84da68f5c8f506c18c196',
        'info_dict': {
            'id': '2015-0703-001',
            'ext': 'mp4',
            'title': 'Daily Show for July 03, 2015',
            'description': 'md5:80eb927244d6749900de6072c7cc2c86',
        },
    }, {
        'url': 'http://www.democracynow.org/2015/7/3/this_flag_comes_down_today_bree',
        'info_dict': {
            'id': '2015-0703-001',
            'ext': 'mp4',
            'title': '"This Flag Comes Down Today": Bree Newsome Scales SC Capitol Flagpole, Takes Down Confederate Flag',
            'description': 'md5:4d2bc4f0d29f5553c2210a4bc7761a21',
        },
        'params': {
            'skip_download': True,
        },
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)

        webpage = self._download_webpage(url, display_id)

        json_data = self._parse_json(self._search_regex(
            r'<script[^>]+type="text/json"[^>]*>\s*({[^>]+})', webpage, 'json'),
            display_id)

        title = json_data['title']
        formats = []

        video_id = None

        for key in ('file', 'audio', 'video', 'high_res_video'):
            media_url = json_data.get(key, '')
            if not media_url:
                continue
            media_url = re.sub(r'\?.*', '', compat_urlparse.urljoin(url, media_url))
            video_id = video_id or remove_start(os.path.splitext(url_basename(media_url))[0], 'dn')
            formats.append({
                'url': media_url,
                'vcodec': 'none' if key == 'audio' else None,
            })

        self._sort_formats(formats)

        default_lang = 'en'
        subtitles = {}

        def add_subtitle_item(lang, info_dict):
            if lang not in subtitles:
                subtitles[lang] = []
            subtitles[lang].append(info_dict)

        # chapter_file are not subtitles
        if 'caption_file' in json_data:
            add_subtitle_item(default_lang, {
                'url': compat_urlparse.urljoin(url, json_data['caption_file']),
            })

        for subtitle_item in json_data.get('captions', []):
            lang = subtitle_item.get('language', '').lower() or default_lang
            add_subtitle_item(lang, {
                'url': compat_urlparse.urljoin(url, subtitle_item['url']),
            })

        description = self._og_search_description(webpage, default=None)

        return {
            'id': video_id or display_id,
            'title': title,
            'description': description,
            'thumbnail': json_data.get('image'),
            'subtitles': subtitles,
            'formats': formats,
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import unified_strdate

class DFBIE(InfoExtractor):
    IE_NAME = 'tv.dfb.de'
    _VALID_URL = r'https?://tv\.dfb\.de/video/(?P<display_id>[^/]+)/(?P<id>\d+)'

    _TEST = {
        'url': 'http://tv.dfb.de/video/u-19-em-stimmen-zum-spiel-gegen-russland/11633/',
        'md5': 'ac0f98a52a330f700b4b3034ad240649',
        'info_dict': {
            'id': '11633',
            'display_id': 'u-19-em-stimmen-zum-spiel-gegen-russland',
            'ext': 'mp4',
            'title': 'U 19-EM: Stimmen zum Spiel gegen Russland',
            'upload_date': '20150714',
        },
    }

    def _real_extract(self, url):
        display_id, video_id = re.match(self._VALID_URL, url).groups()

        player_info = self._download_xml(
            'http://tv.dfb.de/server/hd_video.php?play=%s' % video_id,
            display_id)
        video_info = player_info.find('video')
        stream_access_url = self._proto_relative_url(video_info.find('url').text.strip())

        formats = []
        # see http://tv.dfb.de/player/js/ajax.js for the method to extract m3u8 formats
        for sa_url in (stream_access_url, stream_access_url + '&area=&format=iphone'):
            stream_access_info = self._download_xml(sa_url, display_id)
            token_el = stream_access_info.find('token')
            manifest_url = token_el.attrib['url'] + '?' + 'hdnea=' + token_el.attrib['auth']
            if '.f4m' in manifest_url:
                formats.extend(self._extract_f4m_formats(
                    manifest_url + '&hdcore=3.2.0',
                    display_id, f4m_id='hds', fatal=False))
            else:
                formats.extend(self._extract_m3u8_formats(
                    manifest_url, display_id, 'mp4',
                    'm3u8_native', m3u8_id='hls', fatal=False))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': video_info.find('title').text,
            'thumbnail': 'http://tv.dfb.de/images/%s_640x360.jpg' % video_id,
            'upload_date': unified_strdate(video_info.find('time_date').text),
            'formats': formats,
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import parse_duration

class DHMIE(InfoExtractor):
    IE_DESC = 'Filmarchiv - Deutsches Historisches Museum'
    _VALID_URL = r'https?://(?:www\.)?dhm\.de/filmarchiv/(?:[^/]+/)+(?P<id>[^/]+)'

    _TESTS = [{
        'url': 'http://www.dhm.de/filmarchiv/die-filme/the-marshallplan-at-work-in-west-germany/',
        'md5': '11c475f670209bf6acca0b2b7ef51827',
        'info_dict': {
            'id': 'the-marshallplan-at-work-in-west-germany',
            'ext': 'flv',
            'title': 'MARSHALL PLAN AT WORK IN WESTERN GERMANY, THE',
            'description': 'md5:1fabd480c153f97b07add61c44407c82',
            'duration': 660,
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }, {
        'url': 'http://www.dhm.de/filmarchiv/02-mapping-the-wall/peter-g/rolle-1/',
        'md5': '09890226332476a3e3f6f2cb74734aa5',
        'info_dict': {
            'id': 'rolle-1',
            'ext': 'flv',
            'title': 'ROLLE 1',
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }]

    def _real_extract(self, url):
        playlist_id = self._match_id(url)

        webpage = self._download_webpage(url, playlist_id)

        playlist_url = self._search_regex(
            r"file\s*:\s*'([^']+)'", webpage, 'playlist url')

        entries = self._extract_xspf_playlist(playlist_url, playlist_id)

        title = self._search_regex(
            [r'dc:title="([^"]+)"', r'<title> »([^<]+)</title>'],
            webpage, 'title').strip()
        description = self._html_search_regex(
            r'
Description:(.+?)
',
            webpage, 'description', default=None)
        duration = parse_duration(self._search_regex(
            r'Length\s*\s*:\s*([^<]+)',
            webpage, 'duration', default=None))

        entries[0].update({
            'title': title,
            'description': description,
            'duration': duration,
        })

        return self.playlist_result(entries, playlist_id)


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import js_to_json

class DiggIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?digg\.com/video/(?P<id>[^/?#&]+)'
    _TESTS = [{
        # JWPlatform via provider
        'url': 'http://digg.com/video/sci-fi-short-jonah-daniel-kaluuya-get-out',
        'info_dict': {
            'id': 'LcqvmS0b',
            'ext': 'mp4',
            'title': "'Get Out' Star Daniel Kaluuya Goes On 'Moby Dick'-Like Journey In Sci-Fi Short 'Jonah'",
            'description': 'md5:541bb847648b6ee3d6514bc84b82efda',
            'upload_date': '20180109',
            'timestamp': 1515530551,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # Youtube via provider
        'url': 'http://digg.com/video/dog-boat-seal-play',
        'only_matching': True,
    }, {
        # vimeo as regular embed
        'url': 'http://digg.com/video/dream-girl-short-film',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)

        webpage = self._download_webpage(url, display_id)

        info = self._parse_json(
            self._search_regex(
                r'(?s)video_info\s*=\s*({.+?});\n', webpage, 'video info',
                default='{}'), display_id, transform_source=js_to_json,
            fatal=False)

        video_id = info.get('video_id')

        if video_id:
            provider = info.get('provider_name')
            if provider == 'youtube':
                return self.url_result(
                    video_id, ie='Youtube', video_id=video_id)
            elif provider == 'jwplayer':
                return self.url_result(
                    'jwplatform:%s' % video_id, ie='JWPlatform',
                    video_id=video_id)

        return self.url_result(url, 'Generic')


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import int_or_none

class DigitekaIE(InfoExtractor):
    _VALID_URL = r'''(?x)
        https?://(?:www\.)?(?:digiteka\.net|ultimedia\.com)/
        (?:
            deliver/
            (?P<embed_type>
                generic|
                musique
            )
            (?:/[^/]+)*/
            (?:
                src|
                article
            )|
            default/index/video
            (?P<site_type>
                generic|
                music
            )
            /id
        )/(?P<id>[\d+a-z]+)'''
    _TESTS = [{
        # news
        'url': 'https://www.ultimedia.com/default/index/videogeneric/id/s8uk0r',
        'md5': '276a0e49de58c7e85d32b057837952a2',
        'info_dict': {
            'id': 's8uk0r',
            'ext': 'mp4',
            'title': 'Loi sur la fin de vie: le texte prévoit un renforcement des directives anticipées',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 74,
            'upload_date': '20150317',
            'timestamp': 1426604939,
            'uploader_id': '3fszv',
        },
    }, {
        # music
        'url': 'https://www.ultimedia.com/default/index/videomusic/id/xvpfp8',
        'md5': '2ea3513813cf230605c7e2ffe7eca61c',
        'info_dict': {
            'id': 'xvpfp8',
            'ext': 'mp4',
            'title': 'Two - C\'est La Vie (clip)',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 233,
            'upload_date': '20150224',
            'timestamp': 1424760500,
            'uploader_id': '3rfzk',
        },
    }, {
        'url': 'https://www.digiteka.net/deliver/generic/iframe/mdtk/01637594/src/lqm3kl/zone/1/showtitle/1/autoplay/yes',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_url(webpage):
        mobj = re.search(
            r'<(?:iframe|script)[^>]+src=["\'](?P<url>(?:https?:)?//(?:www\.)?ultimedia\.com/deliver/(?:generic|musique)(?:/[^/]+)*/(?:src|article)/[\d+a-z]+)',
            webpage)
        if mobj:
            return mobj.group('url')

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')
        video_type = mobj.group('embed_type') or mobj.group('site_type')
        if video_type == 'music':
            video_type = 'musique'

        deliver_info = self._download_json(
            'http://www.ultimedia.com/deliver/video?video=%s&topic=%s' % (video_id, video_type),
            video_id)

        yt_id = deliver_info.get('yt_id')
        if yt_id:
            return self.url_result(yt_id, 'Youtube')

        jwconf = deliver_info['jwconf']

        formats = []
        for source in jwconf['playlist'][0]['sources']:
            formats.append({
                'url': source['file'],
                'format_id': source.get('label'),
            })

        self._sort_formats(formats)

        title = deliver_info['title']
        thumbnail = jwconf.get('image')
        duration = int_or_none(deliver_info.get('duration'))
        timestamp = int_or_none(deliver_info.get('release_time'))
        uploader_id = deliver_info.get('owner_id')

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
            'duration': duration,
            'timestamp': timestamp,
            'uploader_id': uploader_id,
            'formats': formats,
        }


code:
from __future__ import unicode_literals

import random
import re
import string

from .discoverygo import DiscoveryGoBaseIE
from ..compat import compat_urllib_parse_unquote
from ..utils import ExtractorError
from ..compat import compat_HTTPError

class DiscoveryIE(DiscoveryGoBaseIE):
    _VALID_URL = r'''(?x)https?://
        (?P<site>
            go\.discovery|
            www\.
                (?:
                    investigationdiscovery|
                    discoverylife|
                    animalplanet|
                    ahctv|
                    destinationamerica|
                    sciencechannel|
                    tlc
                )|
            watch\.
                (?:
                    hgtv|
                    foodnetwork|
                    travelchannel|
                    diynetwork|
                    cookingchanneltv|
                    motortrend
                )
        )\.com/tv-shows/(?P<show_slug>[^/]+)/(?:video|full-episode)s/(?P<id>[^./?#]+)'''
    _TESTS = [{
        'url': 'https://go.discovery.com/tv-shows/cash-cab/videos/riding-with-matthew-perry',
        'info_dict': {
            'id': '5a2f35ce6b66d17a5026e29e',
            'ext': 'mp4',
            'title': 'Riding with Matthew Perry',
            'description': 'md5:a34333153e79bc4526019a5129e7f878',
            'duration': 84,
        },
        'params': {
            'skip_download': True,  # requires ffmpeg
        }
    }, {
        'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
        'only_matching': True,
    }, {
        'url': 'https://go.discovery.com/tv-shows/alaskan-bush-people/videos/follow-your-own-road',
        'only_matching': True,
    }, {
        # using `show_slug` is important to get the correct video data
        'url': 'https://www.sciencechannel.com/tv-shows/mythbusters-on-science/full-episodes/christmas-special',
        'only_matching': True,
    }]
    _GEO_COUNTRIES = ['US']
    _GEO_BYPASS = False
    _API_BASE_URL = 'https://api.discovery.com/v1/'

    def _real_extract(self, url):
        site, show_slug, display_id = re.match(self._VALID_URL, url).groups()

        access_token = None
        cookies = self._get_cookies(url)

        # prefer Affiliate Auth Token over Anonymous Auth Token
        auth_storage_cookie = cookies.get('eosAf') or cookies.get('eosAn')
        if auth_storage_cookie and auth_storage_cookie.value:
            auth_storage = self._parse_json(compat_urllib_parse_unquote(
                compat_urllib_parse_unquote(auth_storage_cookie.value)),
                display_id, fatal=False) or {}
            access_token = auth_storage.get('a') or auth_storage.get('access_token')

        if not access_token:
            access_token = self._download_json(
                'https://%s.com/anonymous' % site, display_id,
                'Downloading token JSON metadata', query={
                    'authRel': 'authorization',
                    'client_id': '3020a40c2356a645b4b4',
                    'nonce': ''.join([random.choice(string.ascii_letters) for _ in range(32)]),
                    'redirectUri': 'https://www.discovery.com/',
                })['access_token']

        headers = self.geo_verification_headers()
        headers['Authorization'] = 'Bearer ' + access_token

        try:
            video = self._download_json(
                self._API_BASE_URL + 'content/videos',
                display_id, 'Downloading content JSON metadata',
                headers=headers, query={
                    'embed': 'show.name',
                    'fields': 'authenticated,description.detailed,duration,episodeNumber,id,name,parental.rating,season.number,show,tags',
                    'slug': display_id,
                    'show_slug': show_slug,
                })[0]
            video_id = video['id']
            stream = self._download_json(
                self._API_BASE_URL + 'streaming/video/' + video_id,
                display_id, 'Downloading streaming JSON metadata', headers=headers)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code in (401, 403):
                e_description = self._parse_json(
                    e.cause.read().decode(), display_id)['description']
                if 'resource not available for country' in e_description:
                    self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
                if 'Authorized Networks' in e_description:
                    raise ExtractorError(
                        'This video is only available via cable service provider subscription that'
                        ' is not currently supported. You may want to use --cookies.', expected=True)
                raise ExtractorError(e_description)
            raise

        return self._extract_video_info(video, stream, display_id)


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    extract_attributes,
    ExtractorError,
    int_or_none,
    parse_age_limit,
    remove_end,
    unescapeHTML,
    url_or_none,
)

class DiscoveryGoBaseIE(InfoExtractor):
    _VALID_URL_TEMPLATE = r'''(?x)https?://(?:www\.)?(?:
            discovery|
            investigationdiscovery|
            discoverylife|
            animalplanet|
            ahctv|
            destinationamerica|
            sciencechannel|
            tlc|
            velocitychannel
        )go\.com/%s(?P<id>[^/?#&]+)'''

    def _extract_video_info(self, video, stream, display_id):
        title = video['name']

        if not stream:
            if video.get('authenticated') is True:
                raise ExtractorError(
                    'This video is only available via cable service provider subscription that'
                    ' is not currently supported. You may want to use --cookies.', expected=True)
            else:
                raise ExtractorError('Unable to find stream')
        STREAM_URL_SUFFIX = 'streamUrl'
        formats = []
        for stream_kind in ('', 'hds'):
            suffix = STREAM_URL_SUFFIX.capitalize() if stream_kind else STREAM_URL_SUFFIX
            stream_url = stream.get('%s%s' % (stream_kind, suffix))
            if not stream_url:
                continue
            if stream_kind == '':
                formats.extend(self._extract_m3u8_formats(
                    stream_url, display_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            elif stream_kind == 'hds':
                formats.extend(self._extract_f4m_formats(
                    stream_url, display_id, f4m_id=stream_kind, fatal=False))
        self._sort_formats(formats)

        video_id = video.get('id') or display_id
        description = video.get('description', {}).get('detailed')
        duration = int_or_none(video.get('duration'))

        series = video.get('show', {}).get('name')
        season_number = int_or_none(video.get('season', {}).get('number'))
        episode_number = int_or_none(video.get('episodeNumber'))

        tags = video.get('tags')
        age_limit = parse_age_limit(video.get('parental', {}).get('rating'))

        subtitles = {}
        captions = stream.get('captions')
        if isinstance(captions, list):
            for caption in captions:
                subtitle_url = url_or_none(caption.get('fileUrl'))
                if not subtitle_url or not subtitle_url.startswith('http'):
                    continue
                lang = caption.get('fileLang', 'en')
                ext = determine_ext(subtitle_url)
                subtitles.setdefault(lang, []).append({
                    'url': subtitle_url,
                    'ext': 'ttml' if ext == 'xml' else ext,
                })

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'duration': duration,
            'series': series,
            'season_number': season_number,
            'episode_number': episode_number,
            'tags': tags,
            'age_limit': age_limit,
            'formats': formats,
            'subtitles': subtitles,
        }

class DiscoveryGoIE(DiscoveryGoBaseIE):
    _VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % r'(?:[^/]+/)+'
    _GEO_COUNTRIES = ['US']
    _TEST = {
        'url': 'https://www.discoverygo.com/bering-sea-gold/reaper-madness/',
        'info_dict': {
            'id': '58c167d86b66d12f2addeb01',
            'ext': 'mp4',
            'title': 'Reaper Madness',
            'description': 'md5:09f2c625c99afb8946ed4fb7865f6e78',
            'duration': 2519,
            'series': 'Bering Sea Gold',
            'season_number': 8,
            'episode_number': 6,
            'age_limit': 14,
        },
    }

    def _real_extract(self, url):
        display_id = self._match_id(url)

        webpage = self._download_webpage(url, display_id)

        container = extract_attributes(
            self._search_regex(
                r'(<div[^>]+class=["\']video-player-container[^>]+>)',
                webpage, 'video container'))

        video = self._parse_json(
            container.get('data-video') or container.get('data-json'),
            display_id)

        stream = video.get('stream')

        return self._extract_video_info(video, stream, display_id)

class DiscoveryGoPlaylistIE(DiscoveryGoBaseIE):
    _VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % ''
    _TEST = {
        'url': 'https://www.discoverygo.com/bering-sea-gold/',
        'info_dict': {
            'id': 'bering-sea-gold',
            'title': 'Bering Sea Gold',
            'description': 'md5:cc5c6489835949043c0cc3ad66c2fa0e',
        },
        'playlist_mincount': 6,
    }

    @classmethod
    def suitable(cls, url):
        return False if DiscoveryGoIE.suitable(url) else super(
            DiscoveryGoPlaylistIE, cls).suitable(url)

    def _real_extract(self, url):
        display_id = self._match_id(url)

        webpage = self._download_webpage(url, display_id)

        entries = []
        for mobj in re.finditer(r'data-json=(["\'])(?P<json>{.+?})\1', webpage):
            data = self._parse_json(
                mobj.group('json'), display_id,
                transform_source=unescapeHTML, fatal=False)
            if not isinstance(data, dict) or data.get('type') != 'episode':
                continue
            episode_url = data.get('socialUrl')
            if not episode_url:
                continue
            entries.append(self.url_result(
                episode_url, ie=DiscoveryGoIE.ie_key(),
                video_id=data.get('id')))

        return self.playlist_result(
            entries, display_id,
            remove_end(self._og_search_title(
                webpage, fatal=False), ' | Discovery GO'),
            self._og_search_description(webpage))


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .dplay import DPlayIE

class DiscoveryNetworksDeIE(DPlayIE):
    _VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:tlc|dmax)\.de|dplay\.co\.uk)/(?:programme|show)/(?P<programme>[^/]+)/video/(?P<alternate_id>[^/]+)'

    _TESTS = [{
        'url': 'https://www.tlc.de/programme/breaking-amish/video/die-welt-da-drauen/DCB331270001100',
        'info_dict': {
            'id': '78867',
            'ext': 'mp4',
            'title': 'Die Welt da draußen',
            'description': 'md5:61033c12b73286e409d99a41742ef608',
            'timestamp': 1554069600,
            'upload_date': '20190331',
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.dmax.de/programme/dmax-highlights/video/tuning-star-sidney-hoffmann-exklusiv-bei-dmax/191023082312316',
        'only_matching': True,
    }, {
        'url': 'https://www.dplay.co.uk/show/ghost-adventures/video/hotel-leger-103620/EHD_280313B',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        domain, programme, alternate_id = re.match(self._VALID_URL, url).groups()
        country = 'GB' if domain == 'dplay.co.uk' else 'DE'
        realm = 'questuk' if country == 'GB' else domain.replace('.', '')
        return self._get_disco_api_info(
            url, '%s/%s' % (programme, alternate_id),
            'sonic-eu1-prod.disco-api.com', realm, country)


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import parse_duration

class DiscoveryVRIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?discoveryvr\.com/watch/(?P<id>[^/?#]+)'
    _TEST = {
        'url': 'http://www.discoveryvr.com/watch/discovery-vr-an-introduction',
        'md5': '32b1929798c464a54356378b7912eca4',
        'info_dict': {
            'id': 'discovery-vr-an-introduction',
            'ext': 'mp4',
            'title': 'Discovery VR - An Introduction',
            'description': 'md5:80d418a10efb8899d9403e61d8790f06',
        }
    }

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)

        bootstrap_data = self._search_regex(
            r'root\.DVR\.bootstrapData\s+=\s+"({.+?})";',
            webpage, 'bootstrap data')
        bootstrap_data = self._parse_json(
            bootstrap_data.encode('utf-8').decode('unicode_escape'),
            display_id)
        videos = self._parse_json(bootstrap_data['videos'], display_id)['allVideos']
        video_data = next(video for video in videos if video.get('slug') == display_id)

        series = video_data.get('showTitle')
        title = episode = video_data.get('title') or series
        if series and series != title:
            title = '%s - %s' % (series, title)

        formats = []
        for f, format_id in (('cdnUriM3U8', 'mobi'), ('webVideoUrlSd', 'sd'), ('webVideoUrlHd', 'hd')):
            f_url = video_data.get(f)
            if not f_url:
                continue
            formats.append({
                'format_id': format_id,
                'url': f_url,
            })

        return {
            'id': display_id,
            'display_id': display_id,
            'title': title,
            'description': video_data.get('description'),
            'thumbnail': video_data.get('thumbnail'),
            'duration': parse_duration(video_data.get('runTime')),
            'formats': formats,
            'episode': episode,
            'series': series,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    unified_strdate,
    compat_str,
    determine_ext,
    ExtractorError,
    update_url_query,
)

class DisneyIE(InfoExtractor):
    _VALID_URL = r'''(?x)
        https?://(?P<domain>(?:[^/]+\.)?(?:disney\.[a-z]{2,3}(?:\.[a-z]{2})?|disney(?:(?:me|latino)\.com|turkiye\.com\.tr|channel\.de)|(?:starwars|marvelkids)\.com))/(?:(?:embed/|(?:[^/]+/)+[\w-]+-)(?P<id>[a-z0-9]{24})|(?:[^/]+/)?(?P<display_id>[^/?#]+))'''
    _TESTS = [{
        # Disney.EmbedVideo
        'url': 'http://video.disney.com/watch/moana-trailer-545ed1857afee5a0ec239977',
        'info_dict': {
            'id': '545ed1857afee5a0ec239977',
            'ext': 'mp4',
            'title': 'Moana - Trailer',
            'description': 'A fun adventure for the entire Family!  Bring home Moana on Digital HD Feb 21 & Blu-ray March 7',
            'upload_date': '20170112',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        # Grill.burger
        'url': 'http://www.starwars.com/video/rogue-one-a-star-wars-story-intro-featurette',
        'info_dict': {
            'id': '5454e9f4e9804a552e3524c8',
            'ext': 'mp4',
            'title': '"Intro" Featurette: Rogue One: A Star Wars Story',
            'upload_date': '20170104',
            'description': 'Go behind-the-scenes of Rogue One: A Star Wars Story in this featurette with Director Gareth Edwards and the cast of the film.',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        'url': 'http://videos.disneylatino.com/ver/spider-man-de-regreso-a-casa-primer-adelanto-543a33a1850bdcfcca13bae2',
        'only_matching': True,
    }, {
        'url': 'http://video.en.disneyme.com/watch/future-worm/robo-carp-2001-544b66002aa7353cdd3f5114',
        'only_matching': True,
    }, {
        'url': 'http://video.disneyturkiye.com.tr/izle/7c-7-cuceler/kimin-sesi-zaten-5456f3d015f6b36c8afdd0e2',
        'only_matching': True,
    }, {
        'url': 'http://disneyjunior.disney.com/embed/546a4798ddba3d1612e4005d',
        'only_matching': True,
    }, {
        'url': 'http://www.starwars.com/embed/54690d1e6c42e5f09a0fb097',
        'only_matching': True,
    }, {
        'url': 'http://spiderman.marvelkids.com/embed/522900d2ced3c565e4cc0677',
        'only_matching': True,
    }, {
        'url': 'http://spiderman.marvelkids.com/videos/contest-of-champions-part-four-clip-1',
        'only_matching': True,
    }, {
        'url': 'http://disneyjunior.en.disneyme.com/dj/watch-my-friends-tigger-and-pooh-promo',
        'only_matching': True,
    }, {
        'url': 'http://disneychannel.de/sehen/soy-luna-folge-118-5518518987ba27f3cc729268',
        'only_matching': True,
    }, {
        'url': 'http://disneyjunior.disney.com/galactech-the-galactech-grab-galactech-an-admiral-rescue',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        domain, video_id, display_id = re.match(self._VALID_URL, url).groups()
        if not video_id:
            webpage = self._download_webpage(url, display_id)
            grill = re.sub(r'"\s*\+\s*"', '', self._search_regex(
                r'Grill\.burger\s*=\s*({.+})\s*:',
                webpage, 'grill data'))
            page_data = next(s for s in self._parse_json(grill, display_id)['stack'] if s.get('type') == 'video')
            video_data = page_data['data'][0]
        else:
            webpage = self._download_webpage(
                'http://%s/embed/%s' % (domain, video_id), video_id)
            page_data = self._parse_json(self._search_regex(
                r'Disney\.EmbedVideo\s*=\s*({.+});',
                webpage, 'embed data'), video_id)
            video_data = page_data['video']

        for external in video_data.get('externals', []):
            if external.get('source') == 'vevo':
                return self.url_result('vevo:' + external['data_id'], 'Vevo')

        video_id = video_data['id']
        title = video_data['title']

        formats = []
        for flavor in video_data.get('flavors', []):
            flavor_format = flavor.get('format')
            flavor_url = flavor.get('url')
            if not flavor_url or not re.match(r'https?://', flavor_url) or flavor_format == 'mp4_access':
                continue
            tbr = int_or_none(flavor.get('bitrate'))
            if tbr == 99999:
                # wrong ks(Kaltura Signature) causes 404 Error
                flavor_url = update_url_query(flavor_url, {'ks': ''})
                m3u8_formats = self._extract_m3u8_formats(
                    flavor_url, video_id, 'mp4',
                    m3u8_id=flavor_format, fatal=False)
                for f in m3u8_formats:
                    # Apple FairPlay
                    if '/fpshls/' in f['url']:
                        continue
                    formats.append(f)
                continue
            format_id = []
            if flavor_format:
                format_id.append(flavor_format)
            if tbr:
                format_id.append(compat_str(tbr))
            ext = determine_ext(flavor_url)
            if flavor_format == 'applehttp' or ext == 'm3u8':
                ext = 'mp4'
            width = int_or_none(flavor.get('width'))
            height = int_or_none(flavor.get('height'))
            formats.append({
                'format_id': '-'.join(format_id),
                'url': flavor_url,
                'width': width,
                'height': height,
                'tbr': tbr,
                'ext': ext,
                'vcodec': 'none' if (width == 0 and height == 0) else None,
            })
        if not formats and video_data.get('expired'):
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, page_data['translations']['video_expired']),
                expected=True)
        self._sort_formats(formats)

        subtitles = {}
        for caption in video_data.get('captions', []):
            caption_url = caption.get('url')
            caption_format = caption.get('format')
            if not caption_url or caption_format.startswith('unknown'):
                continue
            subtitles.setdefault(caption.get('language', 'en'), []).append({
                'url': caption_url,
                'ext': {
                    'webvtt': 'vtt',
                }.get(caption_format, caption_format),
            })

        return {
            'id': video_id,
            'title': title,
            'description': video_data.get('description') or video_data.get('short_desc'),
            'thumbnail': video_data.get('thumb') or video_data.get('thumb_secure'),
            'duration': int_or_none(video_data.get('duration_sec')),
            'upload_date': unified_strdate(video_data.get('publish_date')),
            'formats': formats,
            'subtitles': subtitles,
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    parse_duration,
    remove_end,
    xpath_element,
    xpath_text,
)

class DigitallySpeakingIE(InfoExtractor):
    _VALID_URL = r'https?://(?:s?evt\.dispeak|events\.digitallyspeaking)\.com/(?:[^/]+/)+xml/(?P<id>[^.]+)\.xml'

    _TESTS = [{
        # From http://gdcvault.com/play/1023460/Tenacious-Design-and-The-Interface
        'url': 'http://evt.dispeak.com/ubm/gdc/sf16/xml/840376_BQRC.xml',
        'md5': 'a8efb6c31ed06ca8739294960b2dbabd',
        'info_dict': {
            'id': '840376_BQRC',
            'ext': 'mp4',
            'title': 'Tenacious Design and The Interface of \'Destiny\'',
        },
    }, {
        # From http://www.gdcvault.com/play/1014631/Classic-Game-Postmortem-PAC
        'url': 'http://events.digitallyspeaking.com/gdc/sf11/xml/12396_1299111843500GMPX.xml',
        'only_matching': True,
    }, {
        # From http://www.gdcvault.com/play/1013700/Advanced-Material
        'url': 'http://sevt.dispeak.com/ubm/gdc/eur10/xml/11256_1282118587281VNIT.xml',
        'only_matching': True,
    }]

    def _parse_mp4(self, metadata):
        video_formats = []
        video_root = None

        mp4_video = xpath_text(metadata, './mp4video', default=None)
        if mp4_video is not None:
            mobj = re.match(r'(?P<root>https?://.*?/).*', mp4_video)
            video_root = mobj.group('root')
        if video_root is None:
            http_host = xpath_text(metadata, 'httpHost', default=None)
            if http_host:
                video_root = 'http://%s/' % http_host
        if video_root is None:
            # Hard-coded in http://evt.dispeak.com/ubm/gdc/sf16/custom/player2.js
            # Works for GPUTechConf, too
            video_root = 'http://s3-2u.digitallyspeaking.com/'

        formats = metadata.findall('./MBRVideos/MBRVideo')
        if not formats:
            return None
        for a_format in formats:
            stream_name = xpath_text(a_format, 'streamName', fatal=True)
            video_path = re.match(r'mp4\:(?P<path>.*)', stream_name).group('path')
            url = video_root + video_path
            bitrate = xpath_text(a_format, 'bitrate')
            tbr = int_or_none(bitrate)
            vbr = int_or_none(self._search_regex(
                r'-(\d+)\.mp4', video_path, 'vbr', default=None))
            abr = tbr - vbr if tbr and vbr else None
            video_formats.append({
                'format_id': bitrate,
                'url': url,
                'tbr': tbr,
                'vbr': vbr,
                'abr': abr,
            })
        return video_formats

    def _parse_flv(self, metadata):
        formats = []
        akamai_url = xpath_text(metadata, './akamaiHost', fatal=True)
        audios = metadata.findall('./audios/audio')
        for audio in audios:
            formats.append({
                'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
                'play_path': remove_end(audio.get('url'), '.flv'),
                'ext': 'flv',
                'vcodec': 'none',
                'format_id': audio.get('code'),
            })
        slide_video_path = xpath_text(metadata, './slideVideo', fatal=True)
        formats.append({
            'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
            'play_path': remove_end(slide_video_path, '.flv'),
            'ext': 'flv',
            'format_note': 'slide deck video',
            'quality': -2,
            'preference': -2,
            'format_id': 'slides',
        })
        speaker_video_path = xpath_text(metadata, './speakerVideo', fatal=True)
        formats.append({
            'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
            'play_path': remove_end(speaker_video_path, '.flv'),
            'ext': 'flv',
            'format_note': 'speaker video',
            'quality': -1,
            'preference': -1,
            'format_id': 'speaker',
        })
        return formats

    def _real_extract(self, url):
        video_id = self._match_id(url)

        xml_description = self._download_xml(url, video_id)
        metadata = xpath_element(xml_description, 'metadata')

        video_formats = self._parse_mp4(metadata)
        if video_formats is None:
            video_formats = self._parse_flv(metadata)

        return {
            'id': video_id,
            'formats': video_formats,
            'title': xpath_text(metadata, 'title', fatal=True),
            'duration': parse_duration(xpath_text(metadata, 'endTime')),
            'creator': xpath_text(metadata, 'speaker'),
        }


code:
from __future__ import unicode_literals

import json
import re

from .common import InfoExtractor
from ..utils import int_or_none

class DLiveVODIE(InfoExtractor):
    IE_NAME = 'dlive:vod'
    _VALID_URL = r'https?://(?:www\.)?dlive\.tv/p/(?P<uploader_id>.+?)\+(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://dlive.tv/p/pdp+3mTzOl4WR',
        'info_dict': {
            'id': '3mTzOl4WR',
            'ext': 'mp4',
            'title': 'Minecraft with james charles epic',
            'upload_date': '20190701',
            'timestamp': 1562011015,
            'uploader_id': 'pdp',
        }
    }, {
        'url': 'https://dlive.tv/p/pdpreplay+D-RD-xSZg',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        uploader_id, vod_id = re.match(self._VALID_URL, url).groups()
        broadcast = self._download_json(
            'https://graphigo.prd.dlive.tv/', vod_id,
            data=json.dumps({'query': '''query {
  pastBroadcast(permlink:"%s+%s") {
    content
    createdAt
    length
    playbackUrl
    title
    thumbnailUrl
    viewCount
  }
}''' % (uploader_id, vod_id)}).encode())['data']['pastBroadcast']
        title = broadcast['title']
        formats = self._extract_m3u8_formats(
            broadcast['playbackUrl'], vod_id, 'mp4', 'm3u8_native')
        self._sort_formats(formats)
        return {
            'id': vod_id,
            'title': title,
            'uploader_id': uploader_id,
            'formats': formats,
            'description': broadcast.get('content'),
            'thumbnail': broadcast.get('thumbnailUrl'),
            'timestamp': int_or_none(broadcast.get('createdAt'), 1000),
            'view_count': int_or_none(broadcast.get('viewCount')),
        }

class DLiveStreamIE(InfoExtractor):
    IE_NAME = 'dlive:stream'
    _VALID_URL = r'https?://(?:www\.)?dlive\.tv/(?!p/)(?P<id>[\w.-]+)'

    def _real_extract(self, url):
        display_name = self._match_id(url)
        user = self._download_json(
            'https://graphigo.prd.dlive.tv/', display_name,
            data=json.dumps({'query': '''query {
  userByDisplayName(displayname:"%s") {
    livestream {
      content
      createdAt
      title
      thumbnailUrl
      watchingCount
    }
    username
  }
}''' % display_name}).encode())['data']['userByDisplayName']
        livestream = user['livestream']
        title = livestream['title']
        username = user['username']
        formats = self._extract_m3u8_formats(
            'https://live.prd.dlive.tv/hls/live/%s.m3u8' % username,
            display_name, 'mp4')
        self._sort_formats(formats)
        return {
            'id': display_name,
            'title': self._live_title(title),
            'uploader': display_name,
            'uploader_id': username,
            'formats': formats,
            'description': livestream.get('content'),
            'thumbnail': livestream.get('thumbnailUrl'),
            'is_live': True,
            'timestamp': int_or_none(livestream.get('createdAt'), 1000),
            'view_count': int_or_none(livestream.get('watchingCount')),
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
)

class DotsubIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?dotsub\.com/view/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://dotsub.com/view/9c63db2a-fa95-4838-8e6e-13deafe47f09',
        'md5': '21c7ff600f545358134fea762a6d42b6',
        'info_dict': {
            'id': '9c63db2a-fa95-4838-8e6e-13deafe47f09',
            'ext': 'flv',
            'title': 'MOTIVATION - "It\'s Possible" Best Inspirational Video Ever',
            'description': 'md5:41af1e273edbbdfe4e216a78b9d34ac6',
            'thumbnail': 're:^https?://dotsub.com/media/9c63db2a-fa95-4838-8e6e-13deafe47f09/p',
            'duration': 198,
            'uploader': 'liuxt',
            'timestamp': 1385778501.104,
            'upload_date': '20131130',
            'view_count': int,
        }
    }, {
        'url': 'https://dotsub.com/view/747bcf58-bd59-45b7-8c8c-ac312d084ee6',
        'md5': '2bb4a83896434d5c26be868c609429a3',
        'info_dict': {
            'id': '168006778',
            'ext': 'mp4',
            'title': 'Apartments and flats in Raipur the white symphony',
            'description': 'md5:784d0639e6b7d1bc29530878508e38fe',
            'thumbnail': 're:^https?://dotsub.com/media/747bcf58-bd59-45b7-8c8c-ac312d084ee6/p',
            'duration': 290,
            'timestamp': 1476767794.2809999,
            'upload_date': '20161018',
            'uploader': 'parthivi001',
            'uploader_id': 'user52596202',
            'view_count': int,
        },
        'add_ie': ['Vimeo'],
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)

        info = self._download_json(
            'https://dotsub.com/api/media/%s/metadata' % video_id, video_id)
        video_url = info.get('mediaURI')

        if not video_url:
            webpage = self._download_webpage(url, video_id)
            video_url = self._search_regex(
                [r'<source[^>]+src="([^"]+)"', r'"file"\s*:\s*\'([^\']+)'],
                webpage, 'video url', default=None)
            info_dict = {
                'id': video_id,
                'url': video_url,
                'ext': 'flv',
            }

        if not video_url:
            setup_data = self._parse_json(self._html_search_regex(
                r'(?s)data-setup=([\'"])(?P<content>(?!\1).+?)\1',
                webpage, 'setup data', group='content'), video_id)
            info_dict = {
                '_type': 'url_transparent',
                'url': setup_data['src'],
            }

        info_dict.update({
            'title': info['title'],
            'description': info.get('description'),
            'thumbnail': info.get('screenshotURI'),
            'duration': int_or_none(info.get('duration'), 1000),
            'uploader': info.get('user'),
            'timestamp': float_or_none(info.get('dateCreated'), 1000),
            'view_count': int_or_none(info.get('numberOfViews')),
        })

        return info_dict


code:
# coding: utf-8
from __future__ import unicode_literals

import time
import hashlib
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    unescapeHTML,
    unified_strdate,
    urljoin,
)

class DouyuTVIE(InfoExtractor):
    IE_DESC = '斗鱼'
    _VALID_URL = r'https?://(?:www\.)?douyu(?:tv)?\.com/(?:[^/]+/)*(?P<id>[A-Za-z0-9]+)'
    _TESTS = [{
        'url': 'http://www.douyutv.com/iseven',
        'info_dict': {
            'id': '17732',
            'display_id': 'iseven',
            'ext': 'flv',
            'title': 're:^清晨醒脑！根本停不下来！ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': r're:.*m7show@163\.com.*',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': '7师傅',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://www.douyutv.com/85982',
        'info_dict': {
            'id': '85982',
            'display_id': '85982',
            'ext': 'flv',
            'title': 're:^小漠从零单排记！——CSOL2躲猫猫 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': 'md5:746a2f7a253966a06755a912f0acc0d2',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'douyu小漠',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Room not found',
    }, {
        'url': 'http://www.douyutv.com/17732',
        'info_dict': {
            'id': '17732',
            'display_id': '17732',
            'ext': 'flv',
            'title': 're:^清晨醒脑！根本停不下来！ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': r're:.*m7show@163\.com.*',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': '7师傅',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://www.douyu.com/xiaocang',
        'only_matching': True,
    }, {
        # \"room_id\"
        'url': 'http://www.douyu.com/t/lpl',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)

        if video_id.isdigit():
            room_id = video_id
        else:
            page = self._download_webpage(url, video_id)
            room_id = self._html_search_regex(
                r'"room_id\\?"\s*:\s*(\d+),', page, 'room id')

        # Grab metadata from mobile API
        room = self._download_json(
            'http://m.douyu.com/html5/live?roomId=%s' % room_id, video_id,
            note='Downloading room info')['data']

        # 1 = live, 2 = offline
        if room.get('show_status') == '2':
            raise ExtractorError('Live stream is offline', expected=True)

        # Grab the URL from PC client API
        # The m3u8 url from mobile API requires re-authentication every 5 minutes
        tt = int(time.time())
        signContent = 'lapi/live/thirdPart/getPlay/%s?aid=pcclient&rate=0&time=%d9TUk5fjjUjg9qIMH3sdnh' % (room_id, tt)
        sign = hashlib.md5(signContent.encode('ascii')).hexdigest()
        video_url = self._download_json(
            'http://coapi.douyucdn.cn/lapi/live/thirdPart/getPlay/' + room_id,
            video_id, note='Downloading video URL info',
            query={'rate': 0}, headers={
                'auth': sign,
                'time': str(tt),
                'aid': 'pcclient'
            })['data']['live_url']

        title = self._live_title(unescapeHTML(room['room_name']))
        description = room.get('show_details')
        thumbnail = room.get('room_src')
        uploader = room.get('nickname')

        return {
            'id': room_id,
            'display_id': video_id,
            'url': video_url,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'is_live': True,
        }

class DouyuShowIE(InfoExtractor):
    _VALID_URL = r'https?://v(?:mobile)?\.douyu\.com/show/(?P<id>[0-9a-zA-Z]+)'

    _TESTS = [{
        'url': 'https://v.douyu.com/show/rjNBdvnVXNzvE2yw',
        'md5': '0c2cfd068ee2afe657801269b2d86214',
        'info_dict': {
            'id': 'rjNBdvnVXNzvE2yw',
            'ext': 'mp4',
            'title': '陈一发儿：砒霜 我有个室友系列！04-01 22点场',
            'duration': 7150.08,
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': '陈一发儿',
            'uploader_id': 'XrZwYelr5wbK',
            'uploader_url': 'https://v.douyu.com/author/XrZwYelr5wbK',
            'upload_date': '20170402',
        },
    }, {
        'url': 'https://vmobile.douyu.com/show/rjNBdvnVXNzvE2yw',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        url = url.replace('vmobile.', 'v.')
        video_id = self._match_id(url)

        webpage = self._download_webpage(url, video_id)

        room_info = self._parse_json(self._search_regex(
            r'var\s+\$ROOM\s*=\s*({.+});', webpage, 'room info'), video_id)

        video_info = None

        for trial in range(5):
            # Sometimes Douyu rejects our request. Let's try it more times
            try:
                video_info = self._download_json(
                    'https://vmobile.douyu.com/video/getInfo', video_id,
                    query={'vid': video_id},
                    headers={
                        'Referer': url,
                        'x-requested-with': 'XMLHttpRequest',
                    })
                break
            except ExtractorError:
                self._sleep(1, video_id)

        if not video_info:
            raise ExtractorError('Can\'t fetch video info')

        formats = self._extract_m3u8_formats(
            video_info['data']['video_url'], video_id,
            entry_protocol='m3u8_native', ext='mp4')

        upload_date = unified_strdate(self._html_search_regex(
            r'上传时间：([^<]+)', webpage,
            'upload date', fatal=False))

        uploader = uploader_id = uploader_url = None
        mobj = re.search(
            r'(?m)]+href="/author/([0-9a-zA-Z]+)".+?]+title="([^"]+)"',
            webpage)
        if mobj:
            uploader_id, uploader = mobj.groups()
            uploader_url = urljoin(url, '/author/' + uploader_id)

        return {
            'id': video_id,
            'title': room_info['name'],
            'formats': formats,
            'duration': room_info.get('duration'),
            'thumbnail': room_info.get('pic'),
            'upload_date': upload_date,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'uploader_url': uploader_url,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    determine_ext,
    ExtractorError,
    float_or_none,
    int_or_none,
    unified_timestamp,
)

class DPlayIE(InfoExtractor):
    _VALID_URL = r'''(?x)https?://
        (?P<domain>
            (?:www\.)?(?P<host>dplay\.(?P<country>dk|fi|jp|se|no))|
            (?P<subdomain_country>es|it)\.dplay\.com
        )/[^/]+/(?P<id>[^/]+/[^/?#]+)'''

    _TESTS = [{
        # non geo restricted, via secure api, unsigned download hls URL
        'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
        'info_dict': {
            'id': '13628',
            'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
            'ext': 'mp4',
            'title': 'Svensken lär sig njuta av livet',
            'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8',
            'duration': 2649.856,
            'timestamp': 1365453720,
            'upload_date': '20130408',
            'creator': 'Kanal 5',
            'series': 'Nugammalt - 77 händelser som format Sverige',
            'season_number': 1,
            'episode_number': 1,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
    }, {
        # geo restricted, via secure api, unsigned download hls URL
        'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
        'info_dict': {
            'id': '104465',
            'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
            'ext': 'mp4',
            'title': 'Ted Bundy: Mind Of A Monster',
            'description': 'md5:8b780f6f18de4dae631668b8a9637995',
            'duration': 5290.027,
            'timestamp': 1570694400,
            'upload_date': '20191010',
            'creator': 'ID - Investigation Discovery',
            'series': 'Ted Bundy: Mind Of A Monster',
            'season_number': 1,
            'episode_number': 1,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
    }, {
        # disco-api
        'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7',
        'info_dict': {
            'id': '40206',
            'display_id': 'i-kongens-klr/sesong-1-episode-7',
            'ext': 'mp4',
            'title': 'Episode 7',
            'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf',
            'duration': 2611.16,
            'timestamp': 1516726800,
            'upload_date': '20180123',
            'series': 'I kongens klær',
            'season_number': 1,
            'episode_number': 7,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
        'skip': 'Available for Premium users',
    }, {
        'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/',
        'md5': '2b808ffb00fc47b884a172ca5d13053c',
        'info_dict': {
            'id': '6918',
            'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
            'ext': 'mp4',
            'title': 'Luigi Di Maio: la psicosi di Stanislawskij',
            'description': 'md5:3c7a4303aef85868f867a26f5cc14813',
            'thumbnail': r're:^https?://.*\.jpe?g',
            'upload_date': '20160524',
            'timestamp': 1464076800,
            'series': 'Biografie imbarazzanti',
            'season_number': 1,
            'episode': 'Episode 1',
            'episode_number': 1,
        },
    }, {
        'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/',
        'info_dict': {
            'id': '21652',
            'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1',
            'ext': 'mp4',
            'title': 'Episodio 1',
            'description': 'md5:b9dcff2071086e003737485210675f69',
            'thumbnail': r're:^https?://.*\.png',
            'upload_date': '20180709',
            'timestamp': 1531173540,
            'series': 'La fiebre del oro',
            'season_number': 8,
            'episode': 'Episode 1',
            'episode_number': 1,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
        'only_matching': True,
    }, {
        'url': 'https://www.dplay.jp/video/gold-rush/24086',
        'only_matching': True,
    }]

    def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
        geo_countries = [country.upper()]
        self._initialize_geo_bypass({
            'countries': geo_countries,
        })
        disco_base = 'https://%s/' % disco_host
        token = self._download_json(
            disco_base + 'token', display_id, 'Downloading token',
            query={
                'realm': realm,
            })['data']['attributes']['token']
        headers = {
            'Referer': url,
            'Authorization': 'Bearer ' + token,
        }
        video = self._download_json(
            disco_base + 'content/videos/' + display_id, display_id,
            headers=headers, query={
                'fields[channel]': 'name',
                'fields[image]': 'height,src,width',
                'fields[show]': 'name',
                'fields[tag]': 'name',
                'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
                'include': 'images,primaryChannel,show,tags'
            })
        video_id = video['data']['id']
        info = video['data']['attributes']
        title = info['name'].strip()
        formats = []
        try:
            streaming = self._download_json(
                disco_base + 'playback/videoPlaybackInfo/' + video_id,
                display_id, headers=headers)['data']['attributes']['streaming']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                info = self._parse_json(e.cause.read().decode('utf-8'), display_id)
                error = info['errors'][0]
                error_code = error.get('code')
                if error_code == 'access.denied.geoblocked':
                    self.raise_geo_restricted(countries=geo_countries)
                elif error_code == 'access.denied.missingpackage':
                    self.raise_login_required()
                raise ExtractorError(info['errors'][0]['detail'], expected=True)
            raise
        for format_id, format_dict in streaming.items():
            if not isinstance(format_dict, dict):
                continue
            format_url = format_dict.get('url')
            if not format_url:
                continue
            ext = determine_ext(format_url)
            if format_id == 'dash' or ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    format_url, display_id, mpd_id='dash', fatal=False))
            elif format_id == 'hls' or ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    format_url, display_id, 'mp4',
                    entry_protocol='m3u8_native', m3u8_id='hls',
                    fatal=False))
            else:
                formats.append({
                    'url': format_url,
                    'format_id': format_id,
                })
        self._sort_formats(formats)

        creator = series = None
        tags = []
        thumbnails = []
        included = video.get('included') or []
        if isinstance(included, list):
            for e in included:
                attributes = e.get('attributes')
                if not attributes:
                    continue
                e_type = e.get('type')
                if e_type == 'channel':
                    creator = attributes.get('name')
                elif e_type == 'image':
                    src = attributes.get('src')
                    if src:
                        thumbnails.append({
                            'url': src,
                            'width': int_or_none(attributes.get('width')),
                            'height': int_or_none(attributes.get('height')),
                        })
                if e_type == 'show':
                    series = attributes.get('name')
                elif e_type == 'tag':
                    name = attributes.get('name')
                    if name:
                        tags.append(name)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': info.get('description'),
            'duration': float_or_none(info.get('videoDuration'), 1000),
            'timestamp': unified_timestamp(info.get('publishStart')),
            'series': series,
            'season_number': int_or_none(info.get('seasonNumber')),
            'episode_number': int_or_none(info.get('episodeNumber')),
            'creator': creator,
            'tags': tags,
            'thumbnails': thumbnails,
            'formats': formats,
        }

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        display_id = mobj.group('id')
        domain = mobj.group('domain').lstrip('www.')
        country = mobj.group('country') or mobj.group('subdomain_country')
        host = 'disco-api.' + domain if domain.startswith('dplay.') else 'eu2-prod.disco-api.com'
        return self._get_disco_api_info(
            url, display_id, host, 'dplay' + country, country)


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    js_to_json,
    parse_duration,
    unescapeHTML,
)

class DRBonanzaIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?dr\.dk/bonanza/[^/]+/\d+/[^/]+/(?P<id>\d+)/(?P<display_id>[^/?#&]+)'
    _TEST = {
        'url': 'http://www.dr.dk/bonanza/serie/154/matador/40312/matador---0824-komme-fremmede-',
        'info_dict': {
            'id': '40312',
            'display_id': 'matador---0824-komme-fremmede-',
            'ext': 'mp4',
            'title': 'MATADOR - 08:24. "Komme fremmede".',
            'description': 'md5:77b4c1ac4d4c1b9d610ab4395212ff84',
            'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
            'duration': 4613,
        },
    }

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id, display_id = mobj.group('id', 'display_id')

        webpage = self._download_webpage(url, display_id)

        info = self._parse_html5_media_entries(
            url, webpage, display_id, m3u8_id='hls',
            m3u8_entry_protocol='m3u8_native')[0]
        self._sort_formats(info['formats'])

        asset = self._parse_json(
            self._search_regex(
                r'(?s)currentAsset\s*=\s*({.+?})\s*</script', webpage, 'asset'),
            display_id, transform_source=js_to_json)

        title = unescapeHTML(asset['AssetTitle']).strip()

        def extract(field):
            return self._search_regex(
                r'
]+>\s*
%s:
\s*
\s*
]+>\s*
([^<]+)
' % field,
                webpage, field, default=None)

        info.update({
            'id': asset.get('AssetId') or video_id,
            'display_id': display_id,
            'title': title,
            'description': extract('Programinfo'),
            'duration': parse_duration(extract('Tid')),
            'thumbnail': asset.get('AssetImageUrl'),
        })
        return info