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

import re
import time
import itertools

from .common import InfoExtractor
from .naver import NaverBaseIE
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    merge_dicts,
    remove_start,
    try_get,
    urlencode_postdata,
)

class VLiveIE(NaverBaseIE):
    IE_NAME = 'vlive'
    _VALID_URL = r'https?://(?:(?:www|m)\.)?vlive\.tv/video/(?P<id>[0-9]+)'
    _NETRC_MACHINE = 'vlive'
    _TESTS = [{
        'url': 'http://www.vlive.tv/video/1326',
        'md5': 'cc7314812855ce56de70a06a27314983',
        'info_dict': {
            'id': '1326',
            'ext': 'mp4',
            'title': "[V LIVE] Girl's Day's Broadcast",
            'creator': "Girl's Day",
            'view_count': int,
            'uploader_id': 'muploader_a',
        },
    }, {
        'url': 'http://www.vlive.tv/video/16937',
        'info_dict': {
            'id': '16937',
            'ext': 'mp4',
            'title': '[V LIVE] 첸백시 걍방',
            'creator': 'EXO',
            'view_count': int,
            'subtitles': 'mincount:12',
            'uploader_id': 'muploader_j',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.vlive.tv/video/129100',
        'md5': 'ca2569453b79d66e5b919e5d308bff6b',
        'info_dict': {
            'id': '129100',
            'ext': 'mp4',
            'title': '[V LIVE] [BTS+] Run BTS! 2019 - EP.71 :: Behind the scene',
            'creator': 'BTS+',
            'view_count': int,
            'subtitles': 'mincount:10',
        },
        'skip': 'This video is only available for CH+ subscribers',
    }]

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

    def _real_initialize(self):
        self._login()

    def _login(self):
        email, password = self._get_login_info()
        if None in (email, password):
            return

        def is_logged_in():
            login_info = self._download_json(
                'https://www.vlive.tv/auth/loginInfo', None,
                note='Downloading login info',
                headers={'Referer': 'https://www.vlive.tv/home'})
            return try_get(
                login_info, lambda x: x['message']['login'], bool) or False

        LOGIN_URL = 'https://www.vlive.tv/auth/email/login'
        self._request_webpage(
            LOGIN_URL, None, note='Downloading login cookies')

        self._download_webpage(
            LOGIN_URL, None, note='Logging in',
            data=urlencode_postdata({'email': email, 'pwd': password}),
            headers={
                'Referer': LOGIN_URL,
                'Content-Type': 'application/x-www-form-urlencoded'
            })

        if not is_logged_in():
            raise ExtractorError('Unable to log in', expected=True)

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

        webpage = self._download_webpage(
            'https://www.vlive.tv/video/%s' % video_id, video_id)

        VIDEO_PARAMS_RE = r'\bvlive\.video\.init\(([^)]+)'
        VIDEO_PARAMS_FIELD = 'video params'

        params = self._parse_json(self._search_regex(
            VIDEO_PARAMS_RE, webpage, VIDEO_PARAMS_FIELD, default=''), video_id,
            transform_source=lambda s: '[' + s + ']', fatal=False)

        if not params or len(params) < 7:
            params = self._search_regex(
                VIDEO_PARAMS_RE, webpage, VIDEO_PARAMS_FIELD)
            params = [p.strip(r'"') for p in re.split(r'\s*,\s*', params)]

        status, long_video_id, key = params[2], params[5], params[6]
        status = remove_start(status, 'PRODUCT_')

        if status in ('LIVE_ON_AIR', 'BIG_EVENT_ON_AIR'):
            return self._live(video_id, webpage)
        elif status in ('VOD_ON_AIR', 'BIG_EVENT_INTRO'):
            return self._replay(video_id, webpage, long_video_id, key)

        if status == 'LIVE_END':
            raise ExtractorError('Uploading for replay. Please wait...',
                                 expected=True)
        elif status == 'COMING_SOON':
            raise ExtractorError('Coming soon!', expected=True)
        elif status == 'CANCELED':
            raise ExtractorError('We are sorry, '
                                 'but the live broadcast has been canceled.',
                                 expected=True)
        elif status == 'ONLY_APP':
            raise ExtractorError('Unsupported video type', expected=True)
        else:
            raise ExtractorError('Unknown status %s' % status)

    def _get_common_fields(self, webpage):
        title = self._og_search_title(webpage)
        creator = self._html_search_regex(
            r'<div[^>]+class="info_area"[^>]*>\s*(?:<em[^>]*>.*?</em\s*>\s*)?<a\s+[^>]*>([^<]+)',
            webpage, 'creator', fatal=False)
        thumbnail = self._og_search_thumbnail(webpage)
        return {
            'title': title,
            'creator': creator,
            'thumbnail': thumbnail,
        }

    def _live(self, video_id, webpage):
        init_page = self._download_init_page(video_id)

        live_params = self._search_regex(
            r'"liveStreamInfo"\s*:\s*(".*"),',
            init_page, 'live stream info')
        live_params = self._parse_json(live_params, video_id)
        live_params = self._parse_json(live_params, video_id)

        formats = []
        for vid in live_params.get('resolutions', []):
            formats.extend(self._extract_m3u8_formats(
                vid['cdnUrl'], video_id, 'mp4',
                m3u8_id=vid.get('name'),
                fatal=False, live=True))
        self._sort_formats(formats)

        info = self._get_common_fields(webpage)
        info.update({
            'title': self._live_title(info['title']),
            'id': video_id,
            'formats': formats,
            'is_live': True,
        })
        return info

    def _replay(self, video_id, webpage, long_video_id, key):
        if '' in (long_video_id, key):
            init_page = self._download_init_page(video_id)
            video_info = self._parse_json(self._search_regex(
                (r'(?s)oVideoStatus\s*=\s*({.+?})\s*</script',
                 r'(?s)oVideoStatus\s*=\s*({.+})'), init_page, 'video info'),
                video_id)
            if video_info.get('status') == 'NEED_CHANNEL_PLUS':
                self.raise_login_required(
                    'This video is only available for CH+ subscribers')
            long_video_id, key = video_info['vid'], video_info['inkey']

        return merge_dicts(
            self._get_common_fields(webpage),
            self._extract_video_info(video_id, long_video_id, key))

    def _download_init_page(self, video_id):
        return self._download_webpage(
            'https://www.vlive.tv/video/init/view',
            video_id, note='Downloading live webpage',
            data=urlencode_postdata({'videoSeq': video_id}),
            headers={
                'Referer': 'https://www.vlive.tv/video/%s' % video_id,
                'Content-Type': 'application/x-www-form-urlencoded'
            })

class VLiveChannelIE(InfoExtractor):
    IE_NAME = 'vlive:channel'
    _VALID_URL = r'https?://channels\.vlive\.tv/(?P<id>[0-9A-Z]+)'
    _TEST = {
        'url': 'http://channels.vlive.tv/FCD4B',
        'info_dict': {
            'id': 'FCD4B',
            'title': 'MAMAMOO',
        },
        'playlist_mincount': 110
    }
    _APP_ID = '8c6cc7b45d2568fb668be6e05b6e5a3b'

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

        webpage = self._download_webpage(
            'http://channels.vlive.tv/%s/video' % channel_code, channel_code)

        app_id = None

        app_js_url = self._search_regex(
            r'<script[^>]+src=(["\'])(?P<url>http.+?/app\.js.*?)\1',
            webpage, 'app js', default=None, group='url')

        if app_js_url:
            app_js = self._download_webpage(
                app_js_url, channel_code, 'Downloading app JS', fatal=False)
            if app_js:
                app_id = self._search_regex(
                    r'Global\.VFAN_APP_ID\s*=\s*[\'"]([^\'"]+)[\'"]',
                    app_js, 'app id', default=None)

        app_id = app_id or self._APP_ID

        channel_info = self._download_json(
            'http://api.vfan.vlive.tv/vproxy/channelplus/decodeChannelCode',
            channel_code, note='Downloading decode channel code',
            query={
                'app_id': app_id,
                'channelCode': channel_code,
                '_': int(time.time())
            })

        channel_seq = channel_info['result']['channelSeq']
        channel_name = None
        entries = []

        for page_num in itertools.count(1):
            video_list = self._download_json(
                'http://api.vfan.vlive.tv/vproxy/channelplus/getChannelVideoList',
                channel_code, note='Downloading channel list page #%d' % page_num,
                query={
                    'app_id': app_id,
                    'channelSeq': channel_seq,
                    # Large values of maxNumOfRows (~300 or above) may cause
                    # empty responses (see [1]), e.g. this happens for [2] that
                    # has more than 300 videos.
                    # 1. https://github.com/ytdl-org/youtube-dl/issues/13830
                    # 2. http://channels.vlive.tv/EDBF.
                    'maxNumOfRows': 100,
                    '_': int(time.time()),
                    'pageNo': page_num
                }
            )

            if not channel_name:
                channel_name = try_get(
                    video_list,
                    lambda x: x['result']['channelInfo']['channelName'],
                    compat_str)

            videos = try_get(
                video_list, lambda x: x['result']['videoList'], list)
            if not videos:
                break

            for video in videos:
                video_id = video.get('videoSeq')
                if not video_id:
                    continue
                video_id = compat_str(video_id)
                entries.append(
                    self.url_result(
                        'http://www.vlive.tv/video/%s' % video_id,
                        ie=VLiveIE.ie_key(), video_id=video_id))

        return self.playlist_result(
            entries, channel_code, channel_name)

class VLivePlaylistIE(InfoExtractor):
    IE_NAME = 'vlive:playlist'
    _VALID_URL = r'https?://(?:(?:www|m)\.)?vlive\.tv/video/(?P<video_id>[0-9]+)/playlist/(?P<id>[0-9]+)'
    _VIDEO_URL_TEMPLATE = 'http://www.vlive.tv/video/%s'
    _TESTS = [{
        # regular working playlist
        'url': 'https://www.vlive.tv/video/117956/playlist/117963',
        'info_dict': {
            'id': '117963',
            'title': '아이돌룸(IDOL ROOM) 41회 - (여자)아이들'
        },
        'playlist_mincount': 10
    }, {
        # playlist with no playlistVideoSeqs
        'url': 'http://www.vlive.tv/video/22867/playlist/22912',
        'info_dict': {
            'id': '22867',
            'ext': 'mp4',
            'title': '[V LIVE] Valentine Day Message from MINA',
            'creator': 'TWICE',
            'view_count': int
        },
        'params': {
            'skip_download': True,
        }
    }]

    def _build_video_result(self, video_id, message):
        self.to_screen(message)
        return self.url_result(
            self._VIDEO_URL_TEMPLATE % video_id,
            ie=VLiveIE.ie_key(), video_id=video_id)

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

        if self._downloader.params.get('noplaylist'):
            return self._build_video_result(
                video_id,
                'Downloading just video %s because of --no-playlist'
                % video_id)

        self.to_screen(
            'Downloading playlist %s - add --no-playlist to just download video'
            % playlist_id)

        webpage = self._download_webpage(
            'http://www.vlive.tv/video/%s/playlist/%s'
            % (video_id, playlist_id), playlist_id)

        raw_item_ids = self._search_regex(
            r'playlistVideoSeqs\s*=\s*(\[[^]]+\])', webpage,
            'playlist video seqs', default=None, fatal=False)

        if not raw_item_ids:
            return self._build_video_result(
                video_id,
                'Downloading just video %s because no playlist was found'
                % video_id)

        item_ids = self._parse_json(raw_item_ids, playlist_id)

        entries = [
            self.url_result(
                self._VIDEO_URL_TEMPLATE % item_id, ie=VLiveIE.ie_key(),
                video_id=compat_str(item_id))
            for item_id in item_ids]

        playlist_name = self._html_search_regex(
            r'<div[^>]+class="[^"]*multicam_playlist[^>]*>\s*<h3[^>]+>([^<]+)',
            webpage, 'playlist title', fatal=False)

        return self.playlist_result(entries, playlist_id, playlist_name)


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

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    NO_DEFAULT,
    sanitized_Request,
    urlencode_postdata,
)

class VodlockerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vodlocker\.(?:com|city)/(?:embed-)?(?P<id>[0-9a-zA-Z]+)(?:\..*?)?'

    _TESTS = [{
        'url': 'http://vodlocker.com/e8wvyzz4sl42',
        'md5': 'ce0c2d18fa0735f1bd91b69b0e54aacf',
        'info_dict': {
            'id': 'e8wvyzz4sl42',
            'ext': 'mp4',
            'title': 'Germany vs Brazil',
            'thumbnail': r're:http://.*\.jpg',
        },
    }]

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

        if any(p in webpage for p in (
                '>THIS FILE WAS DELETED<',
                '>File Not Found<',
                'The file you were looking for could not be found, sorry for any inconvenience.<',
                '>The file was removed')):
            raise ExtractorError('Video %s does not exist' % video_id, expected=True)

        fields = self._hidden_inputs(webpage)

        if fields['op'] == 'download1':
            self._sleep(3, video_id)  # they do detect when requests happen too fast!
            post = urlencode_postdata(fields)
            req = sanitized_Request(url, post)
            req.add_header('Content-type', 'application/x-www-form-urlencoded')
            webpage = self._download_webpage(
                req, video_id, 'Downloading video page')

        def extract_file_url(html, default=NO_DEFAULT):
            return self._search_regex(
                r'file:\s*"(http[^\"]+)",', html, 'file url', default=default)

        video_url = extract_file_url(webpage, default=None)

        if not video_url:
            embed_url = self._search_regex(
                r'<iframe[^>]+src=(["\'])(?P<url>(?:https?://)?vodlocker\.(?:com|city)/embed-.+?)\1',
                webpage, 'embed url', group='url')
            embed_webpage = self._download_webpage(
                embed_url, video_id, 'Downloading embed webpage')
            video_url = extract_file_url(embed_webpage)
            thumbnail_webpage = embed_webpage
        else:
            thumbnail_webpage = webpage

        title = self._search_regex(
            r'id="file_title".*?>\s*(.*?)\s*<(?:br|span)', webpage, 'title')
        thumbnail = self._search_regex(
            r'image:\s*"(http[^\"]+)",', thumbnail_webpage, 'thumbnail', fatal=False)

        formats = [{
            'format_id': 'sd',
            'url': video_url,
        }]

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
            'formats': formats,
        }

repo_name: rrosajp/youtube


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

from .onet import OnetBaseIE

class VODPlIE(OnetBaseIE):
    _VALID_URL = r'https?://vod\.pl/(?:[^/]+/)+(?P<id>[0-9a-zA-Z]+)'

    _TESTS = [{
        'url': 'https://vod.pl/filmy/chlopaki-nie-placza/3ep3jns',
        'md5': 'a7dc3b2f7faa2421aefb0ecaabf7ec74',
        'info_dict': {
            'id': '3ep3jns',
            'ext': 'mp4',
            'title': 'Chłopaki nie płaczą',
            'description': 'md5:f5f03b84712e55f5ac9f0a3f94445224',
            'timestamp': 1463415154,
            'duration': 5765,
            'upload_date': '20160516',
        },
    }, {
        'url': 'https://vod.pl/seriale/belfer-na-planie-praca-kamery-online/2c10heh',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        info_dict = self._extract_from_id(self._search_mvp_id(webpage), webpage)
        info_dict['id'] = video_id
        return info_dict


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

from .common import InfoExtractor
from ..utils import unescapeHTML

class VODPlatformIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www\.)?vod-platform\.net|embed\.kwikmotion\.com)/[eE]mbed/(?P<id>[^/?#]+)'
    _TESTS = [{
        # from http://www.lbcgroup.tv/watch/chapter/29143/52844/%D8%A7%D9%84%D9%86%D8%B5%D8%B1%D8%A9-%D9%81%D9%8A-%D8%B6%D9%8A%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%80-cnn/ar
        'url': 'http://vod-platform.net/embed/RufMcytHDolTH1MuKHY9Fw',
        'md5': '1db2b7249ce383d6be96499006e951fc',
        'info_dict': {
            'id': 'RufMcytHDolTH1MuKHY9Fw',
            'ext': 'mp4',
            'title': 'LBCi News_ النصرة في ضيافة الـ "سي.أن.أن"',
        }
    }, {
        'url': 'http://embed.kwikmotion.com/embed/RufMcytHDolTH1MuKHY9Fw',
        'only_matching': True,
    }]

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

        title = unescapeHTML(self._og_search_title(webpage))
        hidden_inputs = self._hidden_inputs(webpage)

        formats = self._extract_wowza_formats(
            hidden_inputs.get('HiddenmyhHlsLink') or hidden_inputs['HiddenmyDashLink'], video_id, skip_protocols=['f4m', 'smil'])
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'thumbnail': hidden_inputs.get('HiddenThumbnail') or self._og_search_thumbnail(webpage),
            'formats': formats,
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    determine_ext,
    int_or_none,
    urljoin,
)

class VoiceRepublicIE(InfoExtractor):
    _VALID_URL = r'https?://voicerepublic\.com/(?:talks|embed)/(?P<id>[0-9a-z-]+)'
    _TESTS = [{
        'url': 'http://voicerepublic.com/talks/watching-the-watchers-building-a-sousveillance-state',
        'md5': 'b9174d651323f17783000876347116e3',
        'info_dict': {
            'id': '2296',
            'display_id': 'watching-the-watchers-building-a-sousveillance-state',
            'ext': 'm4a',
            'title': 'Watching the Watchers: Building a Sousveillance State',
            'description': 'Secret surveillance programs have metadata too. The people and companies that operate secret surveillance programs can be surveilled.',
            'duration': 1556,
            'view_count': int,
        }
    }, {
        'url': 'http://voicerepublic.com/embed/watching-the-watchers-building-a-sousveillance-state',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        if '>Queued for processing, please stand by...<' in webpage:
            raise ExtractorError(
                'Audio is still queued for processing', expected=True)

        talk = self._parse_json(self._search_regex(
            r'initialSnapshot\s*=\s*({.+?});',
            webpage, 'talk'), display_id)['talk']
        title = talk['title']
        formats = [{
            'url': urljoin(url, talk_url),
            'format_id': format_id,
            'ext': determine_ext(talk_url) or format_id,
            'vcodec': 'none',
        } for format_id, talk_url in talk['media_links'].items()]
        self._sort_formats(formats)

        return {
            'id': compat_str(talk.get('id') or display_id),
            'display_id': display_id,
            'title': title,
            'description': talk.get('teaser'),
            'thumbnail': talk.get('image_url'),
            'duration': int_or_none(talk.get('archived_duration')),
            'view_count': int_or_none(talk.get('play_count')),
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    try_get,
    unified_timestamp,
)

class VootIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?voot\.com/(?:[^/]+/)+(?P<id>\d+)'
    _GEO_COUNTRIES = ['IN']
    _TESTS = [{
        'url': 'https://www.voot.com/shows/ishq-ka-rang-safed/1/360558/is-this-the-end-of-kamini-/441353',
        'info_dict': {
            'id': '0_8ledb18o',
            'ext': 'mp4',
            'title': 'Ishq Ka Rang Safed - Season 01 - Episode 340',
            'description': 'md5:06291fbbbc4dcbe21235c40c262507c1',
            'timestamp': 1472162937,
            'upload_date': '20160825',
            'duration': 1146,
            'series': 'Ishq Ka Rang Safed',
            'season_number': 1,
            'episode': 'Is this the end of Kamini?',
            'episode_number': 340,
            'view_count': int,
            'like_count': int,
        },
        'params': {
            'skip_download': True,
        },
        'expected_warnings': ['Failed to download m3u8 information'],
    }, {
        'url': 'https://www.voot.com/kids/characters/mighty-cat-masked-niyander-e-/400478/school-bag-disappears/440925',
        'only_matching': True,
    }, {
        'url': 'https://www.voot.com/movies/pandavas-5/424627',
        'only_matching': True,
    }]

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

        media_info = self._download_json(
            'https://wapi.voot.com/ws/ott/getMediaInfo.json', video_id,
            query={
                'platform': 'Web',
                'pId': 2,
                'mediaId': video_id,
            })

        status_code = try_get(media_info, lambda x: x['status']['code'], int)
        if status_code != 0:
            raise ExtractorError(media_info['status']['message'], expected=True)

        media = media_info['assets']

        entry_id = media['EntryId']
        title = media['MediaName']
        formats = self._extract_m3u8_formats(
            'https://cdnapisec.kaltura.com/p/1982551/playManifest/pt/https/f/applehttp/t/web/e/' + entry_id,
            video_id, 'mp4', m3u8_id='hls')
        self._sort_formats(formats)

        description, series, season_number, episode, episode_number = [None] * 5

        for meta in try_get(media, lambda x: x['Metas'], list) or []:
            key, value = meta.get('Key'), meta.get('Value')
            if not key or not value:
                continue
            if key == 'ContentSynopsis':
                description = value
            elif key == 'RefSeriesTitle':
                series = value
            elif key == 'RefSeriesSeason':
                season_number = int_or_none(value)
            elif key == 'EpisodeMainTitle':
                episode = value
            elif key == 'EpisodeNo':
                episode_number = int_or_none(value)

        return {
            'extractor_key': 'Kaltura',
            'id': entry_id,
            'title': title,
            'description': description,
            'series': series,
            'season_number': season_number,
            'episode': episode,
            'episode_number': episode_number,
            'timestamp': unified_timestamp(media.get('CreationDate')),
            'duration': int_or_none(media.get('Duration')),
            'view_count': int_or_none(media.get('ViewCounter')),
            'like_count': int_or_none(media.get('like_counter')),
            'formats': formats,
        }


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

from .common import InfoExtractor
from .once import OnceIE
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    ExtractorError,
    int_or_none,
)

class VoxMediaVolumeIE(OnceIE):
    _VALID_URL = r'https?://volume\.vox-cdn\.com/embed/(?P<id>[0-9a-f]{9})'

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

        setup = self._parse_json(self._search_regex(
            r'setup\s*=\s*({.+});', webpage, 'setup'), video_id)
        video_data = setup.get('video') or {}
        info = {
            'id': video_id,
            'title': video_data.get('title_short'),
            'description': video_data.get('description_long') or video_data.get('description_short'),
            'thumbnail': video_data.get('brightcove_thumbnail')
        }
        asset = setup.get('asset') or setup.get('params') or {}

        formats = []
        hls_url = asset.get('hls_url')
        if hls_url:
            formats.extend(self._extract_m3u8_formats(
                hls_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
        mp4_url = asset.get('mp4_url')
        if mp4_url:
            tbr = self._search_regex(r'-(\d+)k\.', mp4_url, 'bitrate', default=None)
            format_id = 'http'
            if tbr:
                format_id += '-' + tbr
            formats.append({
                'format_id': format_id,
                'url': mp4_url,
                'tbr': int_or_none(tbr),
            })
        if formats:
            self._sort_formats(formats)
            info['formats'] = formats
            return info

        for provider_video_type in ('ooyala', 'youtube', 'brightcove'):
            provider_video_id = video_data.get('%s_id' % provider_video_type)
            if not provider_video_id:
                continue
            if provider_video_type == 'brightcove':
                info['formats'] = self._extract_once_formats(provider_video_id)
                self._sort_formats(info['formats'])
            else:
                info.update({
                    '_type': 'url_transparent',
                    'url': provider_video_id if provider_video_type == 'youtube' else '%s:%s' % (provider_video_type, provider_video_id),
                    'ie_key': provider_video_type.capitalize(),
                })
            return info
        raise ExtractorError('Unable to find provider video id')

class VoxMediaIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:(?:theverge|vox|sbnation|eater|polygon|curbed|racked|funnyordie)\.com|recode\.net)/(?:[^/]+/)*(?P<id>[^/?]+)'
    _TESTS = [{
        # Volume embed, Youtube
        'url': 'http://www.theverge.com/2014/6/27/5849272/material-world-how-google-discovered-what-software-is-made-of',
        'info_dict': {
            'id': 'j4mLW6x17VM',
            'ext': 'mp4',
            'title': 'Material world: how Google discovered what software is made of',
            'description': 'md5:dfc17e7715e3b542d66e33a109861382',
            'upload_date': '20190710',
            'uploader_id': 'TheVerge',
            'uploader': 'The Verge',
        },
        'add_ie': ['Youtube'],
    }, {
        # Volume embed, Youtube
        'url': 'http://www.theverge.com/2014/10/21/7025853/google-nexus-6-hands-on-photos-video-android-phablet',
        'md5': '4c8f4a0937752b437c3ebc0ed24802b5',
        'info_dict': {
            'id': 'Gy8Md3Eky38',
            'ext': 'mp4',
            'title': 'The Nexus 6: hands-on with Google\'s phablet',
            'description': 'md5:d9f0216e5fb932dd2033d6db37ac3f1d',
            'uploader_id': 'TheVerge',
            'upload_date': '20141021',
            'uploader': 'The Verge',
        },
        'add_ie': ['Youtube'],
        'skip': 'similar to the previous test',
    }, {
        # Volume embed, Youtube
        'url': 'http://www.vox.com/2016/3/31/11336640/mississippi-lgbt-religious-freedom-bill',
        'info_dict': {
            'id': 'YCjDnX-Xzhg',
            'ext': 'mp4',
            'title': "Mississippi's laws are so bad that its anti-LGBTQ law isn't needed to allow discrimination",
            'description': 'md5:fc1317922057de31cd74bce91eb1c66c',
            'uploader_id': 'voxdotcom',
            'upload_date': '20150915',
            'uploader': 'Vox',
        },
        'add_ie': ['Youtube'],
        'skip': 'similar to the previous test',
    }, {
        # youtube embed
        'url': 'http://www.vox.com/2016/3/24/11291692/robot-dance',
        'md5': '83b3080489fb103941e549352d3e0977',
        'info_dict': {
            'id': 'FcNHTJU1ufM',
            'ext': 'mp4',
            'title': 'How "the robot" became the greatest novelty dance of all time',
            'description': 'md5:b081c0d588b8b2085870cda55e6da176',
            'upload_date': '20160324',
            'uploader_id': 'voxdotcom',
            'uploader': 'Vox',
        },
        'add_ie': ['Youtube'],
        'skip': 'Page no longer contain videos',
    }, {
        # SBN.VideoLinkset.entryGroup multiple ooyala embeds
        'url': 'http://www.sbnation.com/college-football-recruiting/2015/2/3/7970291/national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
        'info_dict': {
            'id': 'national-signing-day-rationalizations-itll-be-ok-itll-be-ok',
            'title': '25 lies you will tell yourself on National Signing Day',
            'description': 'It\'s the most self-delusional time of the year, and everyone\'s gonna tell the same lies together!',
        },
        'playlist': [{
            'md5': '721fededf2ab74ae4176c8c8cbfe092e',
            'info_dict': {
                'id': 'p3cThlMjE61VDi_SD9JlIteSNPWVDBB9',
                'ext': 'mp4',
                'title': 'Buddy Hield vs Steph Curry (and the world)',
                'description': 'Let’s dissect only the most important Final Four storylines.',
            },
        }, {
            'md5': 'bf0c5cc115636af028be1bab79217ea9',
            'info_dict': {
                'id': 'BmbmVjMjE6esPHxdALGubTrouQ0jYLHj',
                'ext': 'mp4',
                'title': 'Chasing Cinderella 2016: Syracuse basketball',
                'description': 'md5:e02d56b026d51aa32c010676765a690d',
            },
        }],
        'skip': 'Page no longer contain videos',
    }, {
        # volume embed, Brightcove Once
        'url': 'https://www.recode.net/2014/6/17/11628066/post-post-pc-ceo-the-full-code-conference-video-of-microsofts-satya',
        'md5': '2dbc77b8b0bff1894c2fce16eded637d',
        'info_dict': {
            'id': '1231c973d',
            'ext': 'mp4',
            'title': 'Post-Post-PC CEO: The Full Code Conference Video of Microsoft\'s Satya Nadella',
            'description': 'The longtime veteran was chosen earlier this year as the software giant\'s third leader in its history.',
        },
        'add_ie': ['VoxMediaVolume'],
    }]

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

        def create_entry(provider_video_id, provider_video_type, title=None, description=None):
            video_url = {
                'youtube': '%s',
                'ooyala': 'ooyala:%s',
                'volume': 'http://volume.vox-cdn.com/embed/%s',
            }[provider_video_type] % provider_video_id
            return {
                '_type': 'url_transparent',
                'url': video_url,
                'title': title or self._og_search_title(webpage),
                'description': description or self._og_search_description(webpage),
            }

        entries = []
        entries_data = self._search_regex([
            r'Chorus\.VideoContext\.addVideo\((\[{.+}\])\);',
            r'var\s+entry\s*=\s*({.+});',
            r'SBN\.VideoLinkset\.entryGroup\(\s*(\[.+\])',
        ], webpage, 'video data', default=None)
        if entries_data:
            entries_data = self._parse_json(entries_data, display_id)
            if isinstance(entries_data, dict):
                entries_data = [entries_data]
            for video_data in entries_data:
                provider_video_id = video_data.get('provider_video_id')
                provider_video_type = video_data.get('provider_video_type')
                if provider_video_id and provider_video_type:
                    entries.append(create_entry(
                        provider_video_id, provider_video_type,
                        video_data.get('title'), video_data.get('description')))

        provider_video_id = self._search_regex(
            r'data-ooyala-id="([^"]+)"', webpage, 'ooyala id', default=None)
        if provider_video_id:
            entries.append(create_entry(provider_video_id, 'ooyala'))

        volume_uuid = self._search_regex(
            r'data-volume-uuid="([^"]+)"', webpage, 'volume uuid', default=None)
        if volume_uuid:
            entries.append(create_entry(volume_uuid, 'volume'))

        if len(entries) == 1:
            return entries[0]
        else:
            return self.playlist_result(entries, display_id, self._og_search_title(webpage), self._og_search_description(webpage))


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

import re

from .common import InfoExtractor
from .brightcove import BrightcoveNewIE
from ..utils import (
    int_or_none,
    parse_age_limit,
    smuggle_url,
    unescapeHTML,
)

class VrakIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vrak\.tv/videos\?.*?\btarget=(?P<id>[\d.]+)'
    _TEST = {
        'url': 'http://www.vrak.tv/videos?target=1.2306782&filtre=emission&id=1.1806721',
        'info_dict': {
            'id': '5345661243001',
            'ext': 'mp4',
            'title': 'Obésité, film de hockey et Roseline Filion',
            'timestamp': 1488492126,
            'upload_date': '20170302',
            'uploader_id': '2890187628001',
            'creator': 'VRAK.TV',
            'age_limit': 8,
            'series': 'ALT (Actualité Légèrement Tordue)',
            'episode': 'Obésité, film de hockey et Roseline Filion',
            'tags': list,
        },
        'params': {
            'skip_download': True,
        },
    }
    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/2890187628001/default_default/index.html?videoId=%s'

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

        webpage = self._download_webpage(url, video_id)

        title = self._html_search_regex(
            r'<h\d\b[^>]+\bclass=["\']videoTitle["\'][^>]*>([^<]+)',
            webpage, 'title', default=None) or self._og_search_title(webpage)

        content = self._parse_json(
            self._search_regex(
                r'data-player-options-content=(["\'])(?P<content>{.+?})\1',
                webpage, 'content', default='{}', group='content'),
            video_id, transform_source=unescapeHTML)

        ref_id = content.get('refId') or self._search_regex(
            r'refId":"([^&]+)"', webpage, 'ref id')

        brightcove_id = self._search_regex(
            r'''(?x)
                java\.lang\.String\s+value\s*=\s*["']brightcove\.article\.\d+\.%s
                [^>]*
                java\.lang\.String\s+value\s*=\s*["'](\d+)
            ''' % re.escape(ref_id), webpage, 'brightcove id')

        return {
            '_type': 'url_transparent',
            'ie_key': BrightcoveNewIE.ie_key(),
            'url': smuggle_url(
                self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id,
                {'geo_countries': ['CA']}),
            'id': brightcove_id,
            'description': content.get('description'),
            'creator': content.get('brand'),
            'age_limit': parse_age_limit(content.get('rating')),
            'series': content.get('showName') or content.get(
                'episodeName'),  # this is intentional
            'season_number': int_or_none(content.get('seasonNumber')),
            'episode': title,
            'episode_number': int_or_none(content.get('episodeNumber')),
            'tags': content.get('tags', []),
        }


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

import re

from .common import InfoExtractor
from ..utils import (
    extract_attributes,
    float_or_none,
    get_element_by_class,
    strip_or_none,
    unified_timestamp,
)

class VRTIE(InfoExtractor):
    IE_DESC = 'VRT NWS, Flanders News, Flandern Info and Sporza'
    _VALID_URL = r'https?://(?:www\.)?(?P<site>vrt\.be/vrtnws|sporza\.be)/[a-z]{2}/\d{4}/\d{2}/\d{2}/(?P<id>[^/?&#]+)'
    _TESTS = [{
        'url': 'https://www.vrt.be/vrtnws/nl/2019/05/15/beelden-van-binnenkant-notre-dame-een-maand-na-de-brand/',
        'md5': 'e1663accf5cf13f375f3cd0d10476669',
        'info_dict': {
            'id': 'pbs-pub-7855fc7b-1448-49bc-b073-316cb60caa71$vid-2ca50305-c38a-4762-9890-65cbd098b7bd',
            'ext': 'mp4',
            'title': 'Beelden van binnenkant Notre-Dame, één maand na de brand',
            'description': 'Op maandagavond 15 april ging een deel van het dakgebinte van de Parijse kathedraal in vlammen op.',
            'timestamp': 1557924660,
            'upload_date': '20190515',
            'duration': 31.2,
        },
    }, {
        'url': 'https://sporza.be/nl/2019/05/15/de-belgian-cats-zijn-klaar-voor-het-ek/',
        'md5': '910bba927566e9ab992278f647eb4b75',
        'info_dict': {
            'id': 'pbs-pub-f2c86a46-8138-413a-a4b9-a0015a16ce2c$vid-1f112b31-e58e-4379-908d-aca6d80f8818',
            'ext': 'mp4',
            'title': 'De Belgian Cats zijn klaar voor het EK mét Ann Wauters',
            'timestamp': 1557923760,
            'upload_date': '20190515',
            'duration': 115.17,
        },
    }, {
        'url': 'https://www.vrt.be/vrtnws/en/2019/05/15/belgium_s-eurovision-entry-falls-at-the-first-hurdle/',
        'only_matching': True,
    }, {
        'url': 'https://www.vrt.be/vrtnws/de/2019/05/15/aus-fuer-eliott-im-halbfinale-des-eurosongfestivals/',
        'only_matching': True,
    }]
    _CLIENT_MAP = {
        'vrt.be/vrtnws': 'vrtnieuws',
        'sporza.be': 'sporza',
    }

    def _real_extract(self, url):
        site, display_id = re.match(self._VALID_URL, url).groups()
        webpage = self._download_webpage(url, display_id)
        attrs = extract_attributes(self._search_regex(
            r'(<[^>]+class="vrtvideo"[^>]*>)', webpage, 'vrt video'))

        asset_id = attrs['data-videoid']
        publication_id = attrs.get('data-publicationid')
        if publication_id:
            asset_id = publication_id + '$' + asset_id
        client = attrs.get('data-client') or self._CLIENT_MAP[site]

        title = strip_or_none(get_element_by_class(
            'vrt-title', webpage) or self._html_search_meta(
            ['og:title', 'twitter:title', 'name'], webpage))
        description = self._html_search_meta(
            ['og:description', 'twitter:description', 'description'], webpage)
        if description == '…':
            description = None
        timestamp = unified_timestamp(self._html_search_meta(
            'article:published_time', webpage))

        return {
            '_type': 'url_transparent',
            'id': asset_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': attrs.get('data-posterimage'),
            'timestamp': timestamp,
            'duration': float_or_none(attrs.get('data-duration'), 1000),
            'url': 'https://mediazone.vrt.be/api/v1/%s/assets/%s' % (client, asset_id),
            'ie_key': 'Canvas',
        }


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

import base64
import json
import hashlib
import hmac
import random
import string
import time

from .common import InfoExtractor
from ..compat import (
    compat_HTTPError,
    compat_urllib_parse_urlencode,
    compat_urllib_parse,
)
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
)

class VRVBaseIE(InfoExtractor):
    _API_DOMAIN = None
    _API_PARAMS = {}
    _CMS_SIGNING = {}
    _TOKEN = None
    _TOKEN_SECRET = ''

    def _call_api(self, path, video_id, note, data=None):
        # https://tools.ietf.org/html/rfc5849#section-3
        base_url = self._API_DOMAIN + '/core/' + path
        query = [
            ('oauth_consumer_key', self._API_PARAMS['oAuthKey']),
            ('oauth_nonce', ''.join([random.choice(string.ascii_letters) for _ in range(32)])),
            ('oauth_signature_method', 'HMAC-SHA1'),
            ('oauth_timestamp', int(time.time())),
        ]
        if self._TOKEN:
            query.append(('oauth_token', self._TOKEN))
        encoded_query = compat_urllib_parse_urlencode(query)
        headers = self.geo_verification_headers()
        if data:
            data = json.dumps(data).encode()
            headers['Content-Type'] = 'application/json'
        base_string = '&'.join([
            'POST' if data else 'GET',
            compat_urllib_parse.quote(base_url, ''),
            compat_urllib_parse.quote(encoded_query, '')])
        oauth_signature = base64.b64encode(hmac.new(
            (self._API_PARAMS['oAuthSecret'] + '&' + self._TOKEN_SECRET).encode('ascii'),
            base_string.encode(), hashlib.sha1).digest()).decode()
        encoded_query += '&oauth_signature=' + compat_urllib_parse.quote(oauth_signature, '')
        try:
            return self._download_json(
                '?'.join([base_url, encoded_query]), video_id,
                note='Downloading %s JSON metadata' % note, headers=headers, data=data)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                raise ExtractorError(json.loads(e.cause.read().decode())['message'], expected=True)
            raise

    def _call_cms(self, path, video_id, note):
        if not self._CMS_SIGNING:
            index = self._call_api('index', video_id, 'CMS Signing')
            self._CMS_SIGNING = index.get('cms_signing') or {}
            if not self._CMS_SIGNING:
                for signing_policy in index.get('signing_policies', []):
                    signing_path = signing_policy.get('path')
                    if signing_path and signing_path.startswith('/cms/'):
                        name, value = signing_policy.get('name'), signing_policy.get('value')
                        if name and value:
                            self._CMS_SIGNING[name] = value
        return self._download_json(
            self._API_DOMAIN + path, video_id, query=self._CMS_SIGNING,
            note='Downloading %s JSON metadata' % note, headers=self.geo_verification_headers())

    def _get_cms_resource(self, resource_key, video_id):
        return self._call_api(
            'cms_resource', video_id, 'resource path', data={
                'resource_key': resource_key,
            })['__links__']['cms_resource']['href']

    def _real_initialize(self):
        webpage = self._download_webpage(
            'https://vrv.co/', None, headers=self.geo_verification_headers())
        self._API_PARAMS = self._parse_json(self._search_regex(
            [
                r'window\.__APP_CONFIG__\s*=\s*({.+?})(?:</script>|;)',
                r'window\.__APP_CONFIG__\s*=\s*({.+})'
            ], webpage, 'app config'), None)['cxApiParams']
        self._API_DOMAIN = self._API_PARAMS.get('apiDomain', 'https://api.vrv.co')

class VRVIE(VRVBaseIE):
    IE_NAME = 'vrv'
    _VALID_URL = r'https?://(?:www\.)?vrv\.co/watch/(?P<id>[A-Z0-9]+)'
    _TESTS = [{
        'url': 'https://vrv.co/watch/GR9PNZ396/Hidden-America-with-Jonah-Ray:BOSTON-WHERE-THE-PAST-IS-THE-PRESENT',
        'info_dict': {
            'id': 'GR9PNZ396',
            'ext': 'mp4',
            'title': 'BOSTON: WHERE THE PAST IS THE PRESENT',
            'description': 'md5:4ec8844ac262ca2df9e67c0983c6b83f',
            'uploader_id': 'seeso',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        # movie listing
        'url': 'https://vrv.co/watch/G6NQXZ1J6/Lily-CAT',
        'info_dict': {
            'id': 'G6NQXZ1J6',
            'title': 'Lily C.A.T',
            'description': 'md5:988b031e7809a6aeb60968be4af7db07',
        },
        'playlist_count': 2,
    }]
    _NETRC_MACHINE = 'vrv'

    def _real_initialize(self):
        super(VRVIE, self)._real_initialize()

        email, password = self._get_login_info()
        if email is None:
            return

        token_credentials = self._call_api(
            'authenticate/by:credentials', None, 'Token Credentials', data={
                'email': email,
                'password': password,
            })
        self._TOKEN = token_credentials['oauth_token']
        self._TOKEN_SECRET = token_credentials['oauth_token_secret']

    def _extract_vrv_formats(self, url, video_id, stream_format, audio_lang, hardsub_lang):
        if not url or stream_format not in ('hls', 'dash', 'adaptive_hls'):
            return []
        stream_id_list = []
        if audio_lang:
            stream_id_list.append('audio-%s' % audio_lang)
        if hardsub_lang:
            stream_id_list.append('hardsub-%s' % hardsub_lang)
        format_id = stream_format
        if stream_id_list:
            format_id += '-' + '-'.join(stream_id_list)
        if 'hls' in stream_format:
            adaptive_formats = self._extract_m3u8_formats(
                url, video_id, 'mp4', m3u8_id=format_id,
                note='Downloading %s information' % format_id,
                fatal=False)
        elif stream_format == 'dash':
            adaptive_formats = self._extract_mpd_formats(
                url, video_id, mpd_id=format_id,
                note='Downloading %s information' % format_id,
                fatal=False)
        if audio_lang:
            for f in adaptive_formats:
                if f.get('acodec') != 'none':
                    f['language'] = audio_lang
        return adaptive_formats

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

        object_data = self._call_cms(self._get_cms_resource(
            'cms:/objects/' + video_id, video_id), video_id, 'object')['items'][0]
        resource_path = object_data['__links__']['resource']['href']
        video_data = self._call_cms(resource_path, video_id, 'video')
        title = video_data['title']
        description = video_data.get('description')

        if video_data.get('__class__') == 'movie_listing':
            items = self._call_cms(
                video_data['__links__']['movie_listing/movies']['href'],
                video_id, 'movie listing').get('items') or []
            if len(items) != 1:
                entries = []
                for item in items:
                    item_id = item.get('id')
                    if not item_id:
                        continue
                    entries.append(self.url_result(
                        'https://vrv.co/watch/' + item_id,
                        self.ie_key(), item_id, item.get('title')))
                return self.playlist_result(entries, video_id, title, description)
            video_data = items[0]

        streams_path = video_data['__links__'].get('streams', {}).get('href')
        if not streams_path:
            self.raise_login_required()
        streams_json = self._call_cms(streams_path, video_id, 'streams')

        audio_locale = streams_json.get('audio_locale')
        formats = []
        for stream_type, streams in streams_json.get('streams', {}).items():
            if stream_type in ('adaptive_hls', 'adaptive_dash'):
                for stream in streams.values():
                    formats.extend(self._extract_vrv_formats(
                        stream.get('url'), video_id, stream_type.split('_')[1],
                        audio_locale, stream.get('hardsub_locale')))
        self._sort_formats(formats)

        subtitles = {}
        for k in ('captions', 'subtitles'):
            for subtitle in streams_json.get(k, {}).values():
                subtitle_url = subtitle.get('url')
                if not subtitle_url:
                    continue
                subtitles.setdefault(subtitle.get('locale', 'en-US'), []).append({
                    'url': subtitle_url,
                    'ext': subtitle.get('format', 'ass'),
                })

        thumbnails = []
        for thumbnail in video_data.get('images', {}).get('thumbnails', []):
            thumbnail_url = thumbnail.get('source')
            if not thumbnail_url:
                continue
            thumbnails.append({
                'url': thumbnail_url,
                'width': int_or_none(thumbnail.get('width')),
                'height': int_or_none(thumbnail.get('height')),
            })

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'subtitles': subtitles,
            'thumbnails': thumbnails,
            'description': description,
            'duration': float_or_none(video_data.get('duration_ms'), 1000),
            'uploader_id': video_data.get('channel_id'),
            'series': video_data.get('series_title'),
            'season': video_data.get('season_title'),
            'season_number': int_or_none(video_data.get('season_number')),
            'season_id': video_data.get('season_id'),
            'episode': title,
            'episode_number': int_or_none(video_data.get('episode_number')),
            'episode_id': video_data.get('production_episode_id'),
        }

class VRVSeriesIE(VRVBaseIE):
    IE_NAME = 'vrv:series'
    _VALID_URL = r'https?://(?:www\.)?vrv\.co/series/(?P<id>[A-Z0-9]+)'
    _TEST = {
        'url': 'https://vrv.co/series/G68VXG3G6/The-Perfect-Insider',
        'info_dict': {
            'id': 'G68VXG3G6',
        },
        'playlist_mincount': 11,
    }

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

        seasons_path = self._get_cms_resource(
            'cms:/seasons?series_id=' + series_id, series_id)
        seasons_data = self._call_cms(seasons_path, series_id, 'seasons')

        entries = []
        for season in seasons_data.get('items', []):
            episodes_path = season['__links__']['season/episodes']['href']
            episodes = self._call_cms(episodes_path, series_id, 'episodes')
            for episode in episodes.get('items', []):
                episode_id = episode['id']
                entries.append(self.url_result(
                    'https://vrv.co/watch/' + episode_id,
                    'VRV', episode_id, episode.get('title')))

        return self.playlist_result(entries, series_id)


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

import re

from .common import InfoExtractor
from ..compat import compat_chr
from ..utils import (
    decode_packed_codes,
    ExtractorError,
)

class VShareIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vshare\.io/[dv]/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://vshare.io/d/0f64ce6',
        'md5': '17b39f55b5497ae8b59f5fbce8e35886',
        'info_dict': {
            'id': '0f64ce6',
            'title': 'vl14062007715967',
            'ext': 'mp4',
        }
    }, {
        'url': 'https://vshare.io/v/0f64ce6/width-650/height-430/1',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r'<iframe[^>]+?src=["\'](?P<url>(?:https?:)?//(?:www\.)?vshare\.io/v/[^/?#&]+)',
            webpage)

    def _extract_packed(self, webpage):
        packed = self._search_regex(
            r'(eval\(function.+)', webpage, 'packed code')
        unpacked = decode_packed_codes(packed)
        digits = self._search_regex(r'\[((?:\d+,?)+)\]', unpacked, 'digits')
        digits = [int(digit) for digit in digits.split(',')]
        key_digit = self._search_regex(
            r'fromCharCode\(.+?(\d+)\)}', unpacked, 'key digit')
        chars = [compat_chr(d - int(key_digit)) for d in digits]
        return ''.join(chars)

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

        webpage = self._download_webpage(
            'https://vshare.io/v/%s/width-650/height-430/1' % video_id,
            video_id, headers={'Referer': url})

        title = self._html_search_regex(
            r'<title>([^<]+)</title>', webpage, 'title')
        title = title.split(' - ')[0]

        error = self._html_search_regex(
            r'(?s)<div[^>]+\bclass=["\']xxx-error[^>]+>(.+?)</div', webpage,
            'error', default=None)
        if error:
            raise ExtractorError(error, expected=True)

        info = self._parse_html5_media_entries(
            url, '<video>%s</video>' % self._extract_packed(webpage),
            video_id)[0]

        self._sort_formats(info['formats'])

        info.update({
            'id': video_id,
            'title': title,
        })

        return info


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_str,
)
from ..utils import (
    int_or_none,
    ExtractorError,
)

class VubeIE(InfoExtractor):
    IE_NAME = 'vube'
    IE_DESC = 'Vube.com'
    _VALID_URL = r'https?://vube\.com/(?:[^/]+/)+(?P<id>[\da-zA-Z]{10})\b'

    _TESTS = [
        {
            'url': 'http://vube.com/trending/William+Wei/Y8NUZ69Tf7?t=s',
            'md5': 'e7aabe1f8f1aa826b9e4735e1f9cee42',
            'info_dict': {
                'id': 'Y8NUZ69Tf7',
                'ext': 'mp4',
                'title': 'Best Drummer Ever [HD]',
                'description': 'md5:2d63c4b277b85c2277761c2cf7337d71',
                'thumbnail': r're:^https?://.*\.jpg',
                'uploader': 'William',
                'timestamp': 1406876915,
                'upload_date': '20140801',
                'duration': 258.051,
                'like_count': int,
                'dislike_count': int,
                'comment_count': int,
                'categories': ['amazing', 'hd', 'best drummer ever', 'william wei', 'bucket drumming', 'street drummer', 'epic street drumming'],
            },
            'skip': 'Not accessible from Travis CI server',
        }, {
            'url': 'http://vube.com/Chiara+Grispo+Video+Channel/YL2qNPkqon',
            'md5': 'db7aba89d4603dadd627e9d1973946fe',
            'info_dict': {
                'id': 'YL2qNPkqon',
                'ext': 'mp4',
                'title': 'Chiara Grispo - Price Tag by Jessie J',
                'description': 'md5:8ea652a1f36818352428cb5134933313',
                'thumbnail': r're:^http://frame\.thestaticvube\.com/snap/[0-9x]+/102e7e63057-5ebc-4f5c-4065-6ce4ebde131f\.jpg$',
                'uploader': 'Chiara.Grispo',
                'timestamp': 1388743358,
                'upload_date': '20140103',
                'duration': 170.56,
                'like_count': int,
                'dislike_count': int,
                'comment_count': int,
                'categories': ['pop', 'music', 'cover', 'singing', 'jessie j', 'price tag', 'chiara grispo'],
            },
            'skip': 'Removed due to DMCA',
        },
        {
            'url': 'http://vube.com/SerainaMusic/my-7-year-old-sister-and-i-singing-alive-by-krewella/UeBhTudbfS?t=s&n=1',
            'md5': '5d4a52492d76f72712117ce6b0d98d08',
            'info_dict': {
                'id': 'UeBhTudbfS',
                'ext': 'mp4',
                'title': 'My 7 year old Sister and I singing "Alive" by Krewella',
                'description': 'md5:40bcacb97796339f1690642c21d56f4a',
                'thumbnail': r're:^http://frame\.thestaticvube\.com/snap/[0-9x]+/102265d5a9f-0f17-4f6b-5753-adf08484ee1e\.jpg$',
                'uploader': 'Seraina',
                'timestamp': 1396492438,
                'upload_date': '20140403',
                'duration': 240.107,
                'like_count': int,
                'dislike_count': int,
                'comment_count': int,
                'categories': ['seraina', 'jessica', 'krewella', 'alive'],
            },
            'skip': 'Removed due to DMCA',
        }, {
            'url': 'http://vube.com/vote/Siren+Gene/0nmsMY5vEq?n=2&t=s',
            'md5': '0584fc13b50f887127d9d1007589d27f',
            'info_dict': {
                'id': '0nmsMY5vEq',
                'ext': 'mp4',
                'title': 'Frozen - Let It Go Cover by Siren Gene',
                'description': 'My rendition of "Let It Go" originally sung by Idina Menzel.',
                'thumbnail': r're:^http://frame\.thestaticvube\.com/snap/[0-9x]+/10283ab622a-86c9-4681-51f2-30d1f65774af\.jpg$',
                'uploader': 'Siren',
                'timestamp': 1395448018,
                'upload_date': '20140322',
                'duration': 221.788,
                'like_count': int,
                'dislike_count': int,
                'comment_count': int,
                'categories': ['let it go', 'cover', 'idina menzel', 'frozen', 'singing', 'disney', 'siren gene'],
            },
            'skip': 'Removed due to DMCA',
        }
    ]

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

        video = self._download_json(
            'http://vube.com/t-api/v1/video/%s' % video_id, video_id, 'Downloading video JSON')

        public_id = video['public_id']

        formats = []

        for media in video['media'].get('video', []) + video['media'].get('audio', []):
            if media['transcoding_status'] != 'processed':
                continue
            fmt = {
                'url': 'http://video.thestaticvube.com/video/%s/%s.mp4' % (media['media_resolution_id'], public_id),
                'abr': int(media['audio_bitrate']),
                'format_id': compat_str(media['media_resolution_id']),
            }
            vbr = int(media['video_bitrate'])
            if vbr:
                fmt.update({
                    'vbr': vbr,
                    'height': int(media['height']),
                })
            formats.append(fmt)

        self._sort_formats(formats)

        if not formats and video.get('vst') == 'dmca':
            raise ExtractorError(
                'This video has been removed in response to a complaint received under the US Digital Millennium Copyright Act.',
                expected=True)

        title = video['title']
        description = video.get('description')
        thumbnail = self._proto_relative_url(video.get('thumbnail_src'), scheme='http:')
        uploader = video.get('user_alias') or video.get('channel')
        timestamp = int_or_none(video.get('upload_time'))
        duration = video['duration']
        view_count = video.get('raw_view_count')
        like_count = video.get('total_likes')
        dislike_count = video.get('total_hates')

        comments = video.get('comments')
        comment_count = None
        if comments is None:
            comment_data = self._download_json(
                'http://vube.com/api/video/%s/comment' % video_id,
                video_id, 'Downloading video comment JSON', fatal=False)
            if comment_data is not None:
                comment_count = int_or_none(comment_data.get('total'))
        else:
            comment_count = len(comments)

        categories = [tag['text'] for tag in video['tags']]

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'timestamp': timestamp,
            'duration': duration,
            'view_count': view_count,
            'like_count': like_count,
            'dislike_count': dislike_count,
            'comment_count': comment_count,
            'categories': categories,
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_urllib_parse_urlparse,
)
from ..utils import (
    ExtractorError,
    parse_duration,
    remove_end,
)

class VuClipIE(InfoExtractor):
    _VALID_URL = r'https?://(?:m\.)?vuclip\.com/w\?.*?cid=(?P<id>[0-9]+)'

    _TEST = {
        'url': 'http://m.vuclip.com/w?cid=1129900602&bu=8589892792&frm=w&z=34801&op=0&oc=843169247&section=recommend',
        'info_dict': {
            'id': '1129900602',
            'ext': '3gp',
            'title': 'Top 10 TV Convicts',
            'duration': 733,
        }
    }

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

        ad_m = re.search(
            r'''value="No.*?" onClick="location.href='([^"']+)'"''', webpage)
        if ad_m:
            urlr = compat_urllib_parse_urlparse(url)
            adfree_url = urlr.scheme + '://' + urlr.netloc + ad_m.group(1)
            webpage = self._download_webpage(
                adfree_url, video_id, note='Download post-ad page')

        error_msg = self._html_search_regex(
            r'
(.*?)
', webpage, 'error message',
            default=None)
        if error_msg:
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, error_msg), expected=True)

        # These clowns alternate between two page types
        video_url = self._search_regex(
            r']+href="([^"]+)"[^>]*>]+src="[^"]*/play\.gif',
            webpage, 'video URL', default=None)
        if video_url:
            formats = [{
                'url': video_url,
            }]
        else:
            formats = self._parse_html5_media_entries(url, webpage, video_id)[0]['formats']

        title = remove_end(self._html_search_regex(
            r'<title>(.*?)-\s*Vuclip</title>', webpage, 'title').strip(), ' - Video')

        duration = parse_duration(self._html_search_regex(
            r'[(>]([0-9]+:[0-9]+)(?:<span|\))', webpage, 'duration', fatal=False))

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'duration': duration,
        }


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

import re

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

class VVVVIDIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vvvvid\.it/(?:#!)?(?:show|anime|film|series)/(?P<show_id>\d+)/[^/]+/(?P<season_id>\d+)/(?P<id>[0-9]+)'
    _TESTS = [{
        # video_type == 'video/vvvvid'
        'url': 'https://www.vvvvid.it/#!show/434/perche-dovrei-guardarlo-di-dario-moccia/437/489048/ping-pong',
        'md5': 'b8d3cecc2e981adc3835adf07f6df91b',
        'info_dict': {
            'id': '489048',
            'ext': 'mp4',
            'title': 'Ping Pong',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # video_type == 'video/rcs'
        'url': 'https://www.vvvvid.it/#!show/376/death-note-live-action/377/482493/episodio-01',
        'md5': '33e0edfba720ad73a8782157fdebc648',
        'info_dict': {
            'id': '482493',
            'ext': 'mp4',
            'title': 'Episodio 01',
        },
        'params': {
            'skip_download': True,
        },
    }]
    _conn_id = None

    def _real_initialize(self):
        self._conn_id = self._download_json(
            'https://www.vvvvid.it/user/login',
            None, headers=self.geo_verification_headers())['data']['conn_id']

    def _real_extract(self, url):
        show_id, season_id, video_id = re.match(self._VALID_URL, url).groups()
        response = self._download_json(
            'https://www.vvvvid.it/vvvvid/ondemand/%s/season/%s' % (show_id, season_id),
            video_id, headers=self.geo_verification_headers(), query={
                'conn_id': self._conn_id,
            })
        if response['result'] == 'error':
            raise ExtractorError('%s said: %s' % (
                self.IE_NAME, response['message']), expected=True)

        vid = int(video_id)
        video_data = list(filter(
            lambda episode: episode.get('video_id') == vid, response['data']))[0]
        formats = []

        # vvvvid embed_info decryption algorithm is reverse engineered from function $ds(h) at vvvvid.js
        def ds(h):
            g = "MNOPIJKL89+/4567UVWXQRSTEFGHABCDcdefYZabstuvopqr0123wxyzklmnghij"

            def f(m):
                l = []
                o = 0
                b = False
                m_len = len(m)
                while ((not b) and o < m_len):
                    n = m[o] << 2
                    o += 1
                    k = -1
                    j = -1
                    if o < m_len:
                        n += m[o] >> 4
                        o += 1
                        if o < m_len:
                            k = (m[o - 1] << 4) & 255
                            k += m[o] >> 2
                            o += 1
                            if o < m_len:
                                j = (m[o - 1] << 6) & 255
                                j += m[o]
                                o += 1
                            else:
                                b = True
                        else:
                            b = True
                    else:
                        b = True
                    l.append(n)
                    if k != -1:
                        l.append(k)
                    if j != -1:
                        l.append(j)
                return l

            c = []
            for e in h:
                c.append(g.index(e))

            c_len = len(c)
            for e in range(c_len * 2 - 1, -1, -1):
                a = c[e % c_len] ^ c[(e + 1) % c_len]
                c[e % c_len] = a

            c = f(c)
            d = ''
            for e in c:
                d += chr(e)

            return d

        for quality in ('_sd', ''):
            embed_code = video_data.get('embed_info' + quality)
            if not embed_code:
                continue
            embed_code = ds(embed_code)
            video_type = video_data.get('video_type')
            if video_type in ('video/rcs', 'video/kenc'):
                embed_code = re.sub(r'https?://([^/]+)/z/', r'https://\1/i/', embed_code).replace('/manifest.f4m', '/master.m3u8')
                if video_type == 'video/kenc':
                    kenc = self._download_json(
                        'https://www.vvvvid.it/kenc', video_id, query={
                            'action': 'kt',
                            'conn_id': self._conn_id,
                            'url': embed_code,
                        }, fatal=False) or {}
                    kenc_message = kenc.get('message')
                    if kenc_message:
                        embed_code += '?' + ds(kenc_message)
                formats.extend(self._extract_m3u8_formats(
                    embed_code, video_id, 'mp4',
                    m3u8_id='hls', fatal=False))
            else:
                formats.extend(self._extract_wowza_formats(
                    'http://sb.top-ix.org/videomg/_definst_/mp4:%s/playlist.m3u8' % embed_code, video_id))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_data['title'],
            'formats': formats,
            'thumbnail': video_data.get('thumbnail'),
            'duration': int_or_none(video_data.get('length')),
            'series': video_data.get('show_title'),
            'season_id': season_id,
            'season_number': video_data.get('season_number'),
            'episode_id': str_or_none(video_data.get('id')),
            'episode_number': int_or_none(video_data.get('number')),
            'episode_title': video_data['title'],
            'view_count': int_or_none(video_data.get('views')),
            'like_count': int_or_none(video_data.get('video_likes')),
        }


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

from .common import InfoExtractor
from ..compat import compat_str

class VyboryMosIE(InfoExtractor):
    _VALID_URL = r'https?://vybory\.mos\.ru/(?:#precinct/|account/channels\?.*?\bstation_id=)(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://vybory.mos.ru/#precinct/13636',
        'info_dict': {
            'id': '13636',
            'ext': 'mp4',
            'title': 're:^Участковая избирательная комиссия №2231 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': 'Россия, Москва, улица Введенского, 32А',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        }
    }, {
        'url': 'http://vybory.mos.ru/account/channels?station_id=13636',
        'only_matching': True,
    }]

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

        channels = self._download_json(
            'http://vybory.mos.ru/account/channels?station_id=%s' % station_id,
            station_id, 'Downloading channels JSON')

        formats = []
        for cam_num, (sid, hosts, name, _) in enumerate(channels, 1):
            for num, host in enumerate(hosts, 1):
                formats.append({
                    'url': 'http://%s/master.m3u8?sid=%s' % (host, sid),
                    'ext': 'mp4',
                    'format_id': 'camera%d-host%d' % (cam_num, num),
                    'format_note': '%s, %s' % (name, host),
                })

        info = self._download_json(
            'http://vybory.mos.ru/json/voting_stations/%s/%s.json'
            % (compat_str(station_id)[:3], station_id),
            station_id, 'Downloading station JSON', fatal=False)

        return {
            'id': station_id,
            'title': self._live_title(info['name'] if info else station_id),
            'description': info.get('address'),
            'is_live': True,
            'formats': formats,
        }


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

import re

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

class VzaarIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www|view)\.)?vzaar\.com/(?:videos/)?(?P<id>\d+)'
    _TESTS = [{
        # HTTP and HLS
        'url': 'https://vzaar.com/videos/1152805',
        'md5': 'bde5ddfeb104a6c56a93a06b04901dbf',
        'info_dict': {
            'id': '1152805',
            'ext': 'mp4',
            'title': 'sample video (public)',
        },
    }, {
        'url': 'https://view.vzaar.com/27272/player',
        'md5': '3b50012ac9bbce7f445550d54e0508f2',
        'info_dict': {
            'id': '27272',
            'ext': 'mp3',
            'title': 'MP3',
        },
    }, {
        # hlsAes = true
        'url': 'https://view.vzaar.com/11379930/player',
        'info_dict': {
            'id': '11379930',
            'ext': 'mp4',
            'title': 'Videoaula',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        # with null videoTitle
        'url': 'https://view.vzaar.com/20313539/download',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r'<iframe[^>]+src=["\']((?:https?:)?//(?:view\.vzaar\.com)/[0-9]+)',
            webpage)

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_data = self._download_json(
            'http://view.vzaar.com/v2/%s/video' % video_id, video_id)

        title = video_data.get('videoTitle') or video_id

        formats = []

        source_url = url_or_none(video_data.get('sourceUrl'))
        if source_url:
            f = {
                'url': source_url,
                'format_id': 'http',
                'preference': 1,
            }
            if 'audio' in source_url:
                f.update({
                    'vcodec': 'none',
                    'ext': 'mp3',
                })
            else:
                f.update({
                    'width': int_or_none(video_data.get('width')),
                    'height': int_or_none(video_data.get('height')),
                    'ext': 'mp4',
                    'fps': float_or_none(video_data.get('fps')),
                })
            formats.append(f)

        video_guid = video_data.get('guid')
        usp = video_data.get('usp')
        if video_data.get('uspEnabled') and isinstance(video_guid, compat_str) and isinstance(usp, dict):
            hls_aes = video_data.get('hlsAes')
            qs = '&'.join('%s=%s' % (k, v) for k, v in usp.items())
            url_templ = 'http://%%s.vzaar.com/v5/usp%s/%s/%s.ism%%s?' % ('aes' if hls_aes else '', video_guid, video_id)
            m3u8_formats = self._extract_m3u8_formats(
                url_templ % ('fable', '/.m3u8') + qs, video_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False)
            if hls_aes:
                for f in m3u8_formats:
                    f['_decryption_key_url'] = url_templ % ('goose', '') + qs
            formats.extend(m3u8_formats)

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'thumbnail': self._proto_relative_url(video_data.get('poster')),
            'duration': float_or_none(video_data.get('videoDuration')),
            'timestamp': unified_timestamp(video_data.get('ts')),
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    merge_dicts,
    urljoin,
)

class WakanimIE(InfoExtractor):
    _VALID_URL = r'https://(?:www\.)?wakanim\.tv/[^/]+/v2/catalogue/episode/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.wakanim.tv/de/v2/catalogue/episode/2997/the-asterisk-war-omu-staffel-1-episode-02-omu',
        'info_dict': {
            'id': '2997',
            'ext': 'mp4',
            'title': 'Episode 02',
            'description': 'md5:2927701ea2f7e901de8bfa8d39b2852d',
            'series': 'The Asterisk War  (OmU.)',
            'season_number': 1,
            'episode': 'Episode 02',
            'episode_number': 2,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
    }, {
        # DRM Protected
        'url': 'https://www.wakanim.tv/de/v2/catalogue/episode/7843/sword-art-online-alicization-omu-arc-2-folge-15-omu',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        m3u8_url = urljoin(url, self._search_regex(
            r'file\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage, 'm3u8 url',
            group='url'))
        # https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-content-protection-overview#streaming-urls
        encryption = self._search_regex(
            r'encryption%3D(c(?:enc|bc(?:s-aapl)?))',
            m3u8_url, 'encryption', default=None)
        if encryption and encryption in ('cenc', 'cbcs-aapl'):
            raise ExtractorError('This video is DRM protected.', expected=True)

        formats = self._extract_m3u8_formats(
            m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
            m3u8_id='hls')

        info = self._search_json_ld(webpage, video_id, default={})

        title = self._search_regex(
            (r'<h1[^>]+\bclass=["\']episode_h1[^>]+\btitle=(["\'])(?P<title>(?:(?!\1).)+)\1',
             r'<span[^>]+\bclass=["\']episode_title["\'][^>]*>(?P<title>[^<]+)'),
            webpage, 'title', default=None, group='title')

        return merge_dicts(info, {
            'id': video_id,
            'title': title,
            'formats': formats,
        })


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

import re

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

class WallaIE(InfoExtractor):
    _VALID_URL = r'https?://vod\.walla\.co\.il/[^/]+/(?P<id>\d+)/(?P<display_id>.+)'
    _TEST = {
        'url': 'http://vod.walla.co.il/movie/2642630/one-direction-all-for-one',
        'info_dict': {
            'id': '2642630',
            'display_id': 'one-direction-all-for-one',
            'ext': 'flv',
            'title': 'וואן דיירקשן: ההיסטריה',
            'description': 'md5:de9e2512a92442574cdb0913c49bc4d8',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 3600,
        },
        'params': {
            # rtmp download
            'skip_download': True,
        }
    }

    _SUBTITLE_LANGS = {
        'עברית': 'heb',
    }

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

        video = self._download_xml(
            'http://video2.walla.co.il/?w=null/null/%s/@@/video/flv_pl' % video_id,
            display_id)

        item = video.find('./items/item')

        title = xpath_text(item, './title', 'title')
        description = xpath_text(item, './synopsis', 'description')
        thumbnail = xpath_text(item, './preview_pic', 'thumbnail')
        duration = int_or_none(xpath_text(item, './duration', 'duration'))

        subtitles = {}
        for subtitle in item.findall('./subtitles/subtitle'):
            lang = xpath_text(subtitle, './title')
            subtitles[self._SUBTITLE_LANGS.get(lang, lang)] = [{
                'ext': 'srt',
                'url': xpath_text(subtitle, './src'),
            }]

        formats = []
        for quality in item.findall('./qualities/quality'):
            format_id = xpath_text(quality, './title')
            fmt = {
                'url': 'rtmp://wafla.walla.co.il/vod',
                'play_path': xpath_text(quality, './src'),
                'player_url': 'http://isc.walla.co.il/w9/swf/video_swf/vod/WallaMediaPlayerAvod.swf',
                'page_url': url,
                'ext': 'flv',
                'format_id': xpath_text(quality, './title'),
            }
            m = re.search(r'^(?P<height>\d+)[Pp]', format_id)
            if m:
                fmt['height'] = int(m.group('height'))
            formats.append(fmt)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'duration': duration,
            'formats': formats,
            'subtitles': subtitles,
        }


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

import re

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

class WashingtonPostIE(InfoExtractor):
    IE_NAME = 'washingtonpost'
    _VALID_URL = r'(?:washingtonpost:|https?://(?:www\.)?washingtonpost\.com/video/(?:[^/]+/)*)(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    _EMBED_URL = r'https?://(?:www\.)?washingtonpost\.com/video/c/embed/[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}'
    _TEST = {
        'url': 'https://www.washingtonpost.com/video/c/video/480ba4ee-1ec7-11e6-82c2-a7dcb313287d',
        'md5': '6f537e1334b714eb15f9563bd4b9cdfa',
        'info_dict': {
            'id': '480ba4ee-1ec7-11e6-82c2-a7dcb313287d',
            'ext': 'mp4',
            'title': 'Egypt finds belongings, debris from plane crash',
            'description': 'md5:a17ceee432f215a5371388c1f680bd86',
            'upload_date': '20160520',
            'uploader': 'Reuters',
            'timestamp': 1463778452,
        },
    }

    @classmethod
    def _extract_urls(cls, webpage):
        return re.findall(
            r'<iframe[^>]+\bsrc=["\'](%s)' % cls._EMBED_URL, webpage)

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_data = self._download_json(
            'http://www.washingtonpost.com/posttv/c/videojson/%s?resType=jsonp' % video_id,
            video_id, transform_source=strip_jsonp)[0]['contentConfig']
        title = video_data['title']

        urls = []
        formats = []
        for s in video_data.get('streams', []):
            s_url = s.get('url')
            if not s_url or s_url in urls:
                continue
            urls.append(s_url)
            video_type = s.get('type')
            if video_type == 'smil':
                continue
            elif video_type in ('ts', 'hls') and ('_master.m3u8' in s_url or '_mobile.m3u8' in s_url):
                m3u8_formats = self._extract_m3u8_formats(
                    s_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)
                for m3u8_format in m3u8_formats:
                    width = m3u8_format.get('width')
                    if not width:
                        continue
                    vbr = self._search_regex(
                        r'%d_%d_(\d+)' % (width, m3u8_format['height']), m3u8_format['url'], 'vbr', default=None)
                    if vbr:
                        m3u8_format.update({
                            'vbr': int_or_none(vbr),
                        })
                formats.extend(m3u8_formats)
            else:
                width = int_or_none(s.get('width'))
                vbr = int_or_none(s.get('bitrate'))
                has_width = width != 0
                formats.append({
                    'format_id': (
                        '%s-%d-%d' % (video_type, width, vbr)
                        if width
                        else video_type),
                    'vbr': vbr if has_width else None,
                    'width': width,
                    'height': int_or_none(s.get('height')),
                    'acodec': s.get('audioCodec'),
                    'vcodec': s.get('videoCodec') if has_width else 'none',
                    'filesize': int_or_none(s.get('fileSize')),
                    'url': s_url,
                    'ext': 'mp4',
                    'protocol': 'm3u8_native' if video_type in ('ts', 'hls') else None,
                })
        source_media_url = video_data.get('sourceMediaURL')
        if source_media_url:
            formats.append({
                'format_id': 'source_media',
                'url': source_media_url,
            })
        self._sort_formats(
            formats, ('width', 'height', 'vbr', 'filesize', 'tbr', 'format_id'))

        return {
            'id': video_id,
            'title': title,
            'description': video_data.get('blurb'),
            'uploader': video_data.get('credits', {}).get('source'),
            'formats': formats,
            'duration': int_or_none(video_data.get('videoDuration'), 100),
            'timestamp': int_or_none(
                video_data.get('dateConfig', {}).get('dateFirstPublished'), 1000),
        }

class WashingtonPostArticleIE(InfoExtractor):
    IE_NAME = 'washingtonpost:article'
    _VALID_URL = r'https?://(?:www\.)?washingtonpost\.com/(?:[^/]+/)*(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'http://www.washingtonpost.com/sf/national/2014/03/22/sinkhole-of-bureaucracy/',
        'info_dict': {
            'id': 'sinkhole-of-bureaucracy',
            'title': 'Sinkhole of bureaucracy',
        },
        'playlist': [{
            'md5': 'b9be794ceb56c7267d410a13f99d801a',
            'info_dict': {
                'id': 'fc433c38-b146-11e3-b8b3-44b1d1cd4c1f',
                'ext': 'mp4',
                'title': 'Breaking Points: The Paper Mine',
                'duration': 1290,
                'description': 'Overly complicated paper pushing is nothing new to government bureaucracy. But the way federal retirement applications are filed may be the most outdated. David Fahrenthold explains.',
                'uploader': 'The Washington Post',
                'timestamp': 1395527908,
                'upload_date': '20140322',
            },
        }, {
            'md5': '1fff6a689d8770966df78c8cb6c8c17c',
            'info_dict': {
                'id': '41255e28-b14a-11e3-b8b3-44b1d1cd4c1f',
                'ext': 'mp4',
                'title': 'The town bureaucracy sustains',
                'description': 'Underneath the friendly town of Boyers is a sea of government paperwork. In a disused limestone mine, hundreds of locals now track, file and process retirement applications for the federal government. We set out to find out what it\'s like to do paperwork 230 feet underground.',
                'duration': 2220,
                'timestamp': 1395528005,
                'upload_date': '20140322',
                'uploader': 'The Washington Post',
            },
        }],
    }, {
        'url': 'http://www.washingtonpost.com/blogs/wonkblog/wp/2014/12/31/one-airline-figured-out-how-to-make-sure-its-airplanes-never-disappear/',
        'info_dict': {
            'id': 'one-airline-figured-out-how-to-make-sure-its-airplanes-never-disappear',
            'title': 'One airline figured out how to make sure its airplanes never disappear',
        },
        'playlist': [{
            'md5': 'a7c1b5634ba5e57a6a82cdffa5b1e0d0',
            'info_dict': {
                'id': '0e4bb54c-9065-11e4-a66f-0ca5037a597d',
                'ext': 'mp4',
                'description': 'Washington Post transportation reporter Ashley Halsey III explains why a plane\'s black box needs to be recovered from a crash site instead of having its information streamed in real time throughout the flight.',
                'upload_date': '20141230',
                'uploader': 'The Washington Post',
                'timestamp': 1419974765,
                'title': 'Why black boxes don’t transmit data in real time',
            }
        }]
    }]

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

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

        title = self._og_search_title(webpage)

        uuids = re.findall(r'''(?x)
            (?:
                <div\s+class="posttv-video-embed[^>]*?data-uuid=|
                data-video-uuid=
            )"([^"]+)"''', webpage)
        entries = [self.url_result('washingtonpost:%s' % uuid, 'WashingtonPost', uuid) for uuid in uuids]

        return {
            '_type': 'playlist',
            'entries': entries,
            'id': page_id,
            'title': title,
        }


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

import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    unified_strdate,
    HEADRequest,
    int_or_none,
)

class WatIE(InfoExtractor):
    _VALID_URL = r'(?:wat:|https?://(?:www\.)?wat\.tv/video/.*-)(?P<id>[0-9a-z]+)'
    IE_NAME = 'wat.tv'
    _TESTS = [
        {
            'url': 'http://www.wat.tv/video/soupe-figues-l-orange-aux-epices-6z1uz_2hvf7_.html',
            'info_dict': {
                'id': '11713067',
                'ext': 'mp4',
                'title': 'Soupe de figues à l\'orange et aux épices',
                'description': 'Retrouvez l\'émission "Petits plats en équilibre", diffusée le 18 août 2014.',
                'upload_date': '20140819',
                'duration': 120,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
            'expected_warnings': ['HTTP Error 404'],
        },
        {
            'url': 'http://www.wat.tv/video/gregory-lemarchal-voix-ange-6z1v7_6ygkj_.html',
            'md5': 'b16574df2c3cd1a36ca0098f2a791925',
            'info_dict': {
                'id': '11713075',
                'ext': 'mp4',
                'title': 'Grégory Lemarchal, une voix d\'ange depuis 10 ans (1/3)',
                'upload_date': '20140816',
            },
            'expected_warnings': ["Ce contenu n'est pas disponible pour l'instant."],
        },
    ]

    _FORMATS = (
        (200, 416, 234),
        (400, 480, 270),
        (600, 640, 360),
        (1200, 640, 360),
        (1800, 960, 540),
        (2500, 1280, 720),
    )

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_id = video_id if video_id.isdigit() and len(video_id) > 6 else compat_str(int(video_id, 36))

        # 'contentv4' is used in the website, but it also returns the related
        # videos, we don't need them
        video_data = self._download_json(
            'http://www.wat.tv/interface/contentv4s/' + video_id, video_id)
        video_info = video_data['media']

        error_desc = video_info.get('error_desc')
        if error_desc:
            self.report_warning(
                '%s returned error: %s' % (self.IE_NAME, error_desc))

        chapters = video_info['chapters']
        if chapters:
            first_chapter = chapters[0]

            def video_id_for_chapter(chapter):
                return chapter['tc_start'].split('-')[0]

            if video_id_for_chapter(first_chapter) != video_id:
                self.to_screen('Multipart video detected')
                entries = [self.url_result('wat:%s' % video_id_for_chapter(chapter)) for chapter in chapters]
                return self.playlist_result(entries, video_id, video_info['title'])
            # Otherwise we can continue and extract just one part, we have to use
            # the video id for getting the video url
        else:
            first_chapter = video_info

        title = first_chapter['title']

        def extract_url(path_template, url_type):
            req_url = 'http://www.wat.tv/get/%s' % (path_template % video_id)
            head = self._request_webpage(HEADRequest(req_url), video_id, 'Extracting %s url' % url_type, fatal=False)
            if head:
                red_url = head.geturl()
                if req_url != red_url:
                    return red_url
            return None

        def remove_bitrate_limit(manifest_url):
            return re.sub(r'(?:max|min)_bitrate=\d+&?', '', manifest_url)

        formats = []
        try:
            alt_urls = lambda manifest_url: [re.sub(r'(?:wdv|ssm)?\.ism/', repl + '.ism/', manifest_url) for repl in ('', 'ssm')]
            manifest_urls = self._download_json(
                'http://www.wat.tv/get/webhtml/' + video_id, video_id)
            m3u8_url = manifest_urls.get('hls')
            if m3u8_url:
                m3u8_url = remove_bitrate_limit(m3u8_url)
                for m3u8_alt_url in alt_urls(m3u8_url):
                    formats.extend(self._extract_m3u8_formats(
                        m3u8_alt_url, video_id, 'mp4',
                        'm3u8_native', m3u8_id='hls', fatal=False))
                    formats.extend(self._extract_f4m_formats(
                        m3u8_alt_url.replace('ios', 'web').replace('.m3u8', '.f4m'),
                        video_id, f4m_id='hds', fatal=False))
            mpd_url = manifest_urls.get('mpd')
            if mpd_url:
                mpd_url = remove_bitrate_limit(mpd_url)
                for mpd_alt_url in alt_urls(mpd_url):
                    formats.extend(self._extract_mpd_formats(
                        mpd_alt_url, video_id, mpd_id='dash', fatal=False))
            self._sort_formats(formats)
        except ExtractorError:
            abr = 64
            for vbr, width, height in self._FORMATS:
                tbr = vbr + abr
                format_id = 'http-%s' % tbr
                fmt_url = 'http://dnl.adv.tf1.fr/2/USP-0x0/%s/%s/%s/ssm/%s-%s-64k.mp4' % (video_id[-4:-2], video_id[-2:], video_id, video_id, vbr)
                if self._is_valid_url(fmt_url, video_id, format_id):
                    formats.append({
                        'format_id': format_id,
                        'url': fmt_url,
                        'vbr': vbr,
                        'abr': abr,
                        'width': width,
                        'height': height,
                    })

        date_diffusion = first_chapter.get('date_diffusion') or video_data.get('configv4', {}).get('estatS4')
        upload_date = unified_strdate(date_diffusion) if date_diffusion else None
        duration = None
        files = video_info['files']
        if files:
            duration = int_or_none(files[0].get('duration'))

        return {
            'id': video_id,
            'title': title,
            'thumbnail': first_chapter.get('preview'),
            'description': first_chapter.get('description'),
            'view_count': int_or_none(video_info.get('views')),
            'upload_date': upload_date,
            'duration': duration,
            'formats': formats,
        }


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

import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    int_or_none,
    js_to_json,
    strip_or_none,
    try_get,
    unescapeHTML,
    unified_timestamp,
)

class WatchBoxIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?watchbox\.de/(?P<kind>serien|filme)/(?:[^/]+/)*[^/]+-(?P<id>\d+)'
    _TESTS = [{
        # film
        'url': 'https://www.watchbox.de/filme/free-jimmy-12325.html',
        'info_dict': {
            'id': '341368',
            'ext': 'mp4',
            'title': 'Free Jimmy',
            'description': 'md5:bcd8bafbbf9dc0ef98063d344d7cc5f6',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 4890,
            'age_limit': 16,
            'release_year': 2009,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
        'expected_warnings': ['Failed to download m3u8 information'],
    }, {
        # episode
        'url': 'https://www.watchbox.de/serien/ugly-americans-12231/staffel-1/date-in-der-hoelle-328286.html',
        'info_dict': {
            'id': '328286',
            'ext': 'mp4',
            'title': 'S01 E01 - Date in der Hölle',
            'description': 'md5:2f31c74a8186899f33cb5114491dae2b',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1291,
            'age_limit': 12,
            'release_year': 2010,
            'series': 'Ugly Americans',
            'season_number': 1,
            'episode': 'Date in der Hölle',
            'episode_number': 1,
        },
        'params': {
            'format': 'bestvideo',
            'skip_download': True,
        },
        'expected_warnings': ['Failed to download m3u8 information'],
    }, {
        'url': 'https://www.watchbox.de/serien/ugly-americans-12231/staffel-2/der-ring-des-powers-328270',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        player_config = self._parse_json(
            self._search_regex(
                r'data-player-conf=(["\'])(?P<data>{.+?})\1', webpage,
                'player config', default='{}', group='data'),
            video_id, transform_source=unescapeHTML, fatal=False)

        if not player_config:
            player_config = self._parse_json(
                self._search_regex(
                    r'playerConf\s*=\s*({.+?})\s*;', webpage, 'player config',
                    default='{}'),
                video_id, transform_source=js_to_json, fatal=False) or {}

        source = player_config.get('source') or {}

        video_id = compat_str(source.get('videoId') or video_id)

        devapi = self._download_json(
            'http://api.watchbox.de/devapi/id/%s' % video_id, video_id, query={
                'format': 'json',
                'apikey': 'hbbtv',
            }, fatal=False)

        item = try_get(devapi, lambda x: x['items'][0], dict) or {}

        title = item.get('title') or try_get(
            item, lambda x: x['movie']['headline_movie'],
            compat_str) or source['title']

        formats = []
        hls_url = item.get('media_videourl_hls') or source.get('hls')
        if hls_url:
            formats.extend(self._extract_m3u8_formats(
                hls_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls', fatal=False))
        dash_url = item.get('media_videourl_wv') or source.get('dash')
        if dash_url:
            formats.extend(self._extract_mpd_formats(
                dash_url, video_id, mpd_id='dash', fatal=False))
        mp4_url = item.get('media_videourl')
        if mp4_url:
            formats.append({
                'url': mp4_url,
                'format_id': 'mp4',
                'width': int_or_none(item.get('width')),
                'height': int_or_none(item.get('height')),
                'tbr': int_or_none(item.get('bitrate')),
            })
        self._sort_formats(formats)

        description = strip_or_none(item.get('descr'))
        thumbnail = item.get('media_content_thumbnail_large') or source.get('poster') or item.get('media_thumbnail')
        duration = int_or_none(item.get('media_length') or source.get('length'))
        timestamp = unified_timestamp(item.get('pubDate'))
        view_count = int_or_none(item.get('media_views'))
        age_limit = int_or_none(try_get(item, lambda x: x['movie']['fsk']))
        release_year = int_or_none(try_get(item, lambda x: x['movie']['rel_year']))

        info = {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'duration': duration,
            'timestamp': timestamp,
            'view_count': view_count,
            'age_limit': age_limit,
            'release_year': release_year,
            'formats': formats,
        }

        if kind.lower() == 'serien':
            series = try_get(
                item, lambda x: x['special']['title'],
                compat_str) or source.get('format')
            season_number = int_or_none(self._search_regex(
                r'^S(\d{1,2})\s*E\d{1,2}', title, 'season number',
                default=None) or self._search_regex(
                    r'/staffel-(\d+)/', url, 'season number', default=None))
            episode = source.get('title')
            episode_number = int_or_none(self._search_regex(
                r'^S\d{1,2}\s*E(\d{1,2})', title, 'episode number',
                default=None))
            info.update({
                'series': series,
                'season_number': season_number,
                'episode': episode,
                'episode_number': episode_number,
            })

        return info