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

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

class FilmOnIE(InfoExtractor):
    IE_NAME = 'filmon'
    _VALID_URL = r'(?:https?://(?:www\.)?filmon\.com/vod/view/|filmon:)(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.filmon.com/vod/view/24869-0-plan-9-from-outer-space',
        'info_dict': {
            'id': '24869',
            'ext': 'mp4',
            'title': 'Plan 9 From Outer Space',
            'description': 'Dead human, zombies and vampires',
        },
    }, {
        'url': 'https://www.filmon.com/vod/view/2825-1-popeye-series-1',
        'info_dict': {
            'id': '2825',
            'title': 'Popeye Series 1',
            'description': 'The original series of Popeye.',
        },
        'playlist_mincount': 8,
    }]

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

        try:
            response = self._download_json(
                'https://www.filmon.com/api/vod/movie?id=%s' % video_id,
                video_id)['response']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError):
                errmsg = self._parse_json(e.cause.read().decode(), video_id)['reason']
                raise ExtractorError('%s said: %s' % (self.IE_NAME, errmsg), expected=True)
            raise

        title = response['title']
        description = strip_or_none(response.get('description'))

        if response.get('type_id') == 1:
            entries = [self.url_result('filmon:' + episode_id) for episode_id in response.get('episodes', [])]
            return self.playlist_result(entries, video_id, title, description)

        QUALITY = qualities(('low', 'high'))
        formats = []
        for format_id, stream in response.get('streams', {}).items():
            stream_url = stream.get('url')
            if not stream_url:
                continue
            formats.append({
                'format_id': format_id,
                'url': stream_url,
                'ext': 'mp4',
                'quality': QUALITY(stream.get('quality')),
                'protocol': 'm3u8_native',
            })
        self._sort_formats(formats)

        thumbnails = []
        poster = response.get('poster', {})
        thumbs = poster.get('thumbs', {})
        thumbs['poster'] = poster
        for thumb_id, thumb in thumbs.items():
            thumb_url = thumb.get('url')
            if not thumb_url:
                continue
            thumbnails.append({
                'id': thumb_id,
                'url': thumb_url,
                'width': int_or_none(thumb.get('width')),
                'height': int_or_none(thumb.get('height')),
            })

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': description,
            'thumbnails': thumbnails,
        }

class FilmOnChannelIE(InfoExtractor):
    IE_NAME = 'filmon:channel'
    _VALID_URL = r'https?://(?:www\.)?filmon\.com/(?:tv|channel)/(?P<id>[a-z0-9-]+)'
    _TESTS = [{
        # VOD
        'url': 'http://www.filmon.com/tv/sports-haters',
        'info_dict': {
            'id': '4190',
            'ext': 'mp4',
            'title': 'Sports Haters',
            'description': 'md5:dabcb4c1d9cfc77085612f1a85f8275d',
        },
    }, {
        # LIVE
        'url': 'https://www.filmon.com/channel/filmon-sports',
        'only_matching': True,
    }, {
        'url': 'https://www.filmon.com/tv/2894',
        'only_matching': True,
    }]

    _THUMBNAIL_RES = [
        ('logo', 56, 28),
        ('big_logo', 106, 106),
        ('extra_big_logo', 300, 300),
    ]

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

        try:
            channel_data = self._download_json(
                'http://www.filmon.com/api-v2/channel/' + channel_id, channel_id)['data']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError):
                errmsg = self._parse_json(e.cause.read().decode(), channel_id)['message']
                raise ExtractorError('%s said: %s' % (self.IE_NAME, errmsg), expected=True)
            raise

        channel_id = compat_str(channel_data['id'])
        is_live = not channel_data.get('is_vod') and not channel_data.get('is_vox')
        title = channel_data['title']

        QUALITY = qualities(('low', 'high'))
        formats = []
        for stream in channel_data.get('streams', []):
            stream_url = stream.get('url')
            if not stream_url:
                continue
            if not is_live:
                formats.extend(self._extract_wowza_formats(
                    stream_url, channel_id, skip_protocols=['dash', 'rtmp', 'rtsp']))
                continue
            quality = stream.get('quality')
            formats.append({
                'format_id': quality,
                # this is an m3u8 stream, but we are deliberately not using _extract_m3u8_formats
                # because it doesn't have bitrate variants anyway
                'url': stream_url,
                'ext': 'mp4',
                'quality': QUALITY(quality),
            })
        self._sort_formats(formats)

        thumbnails = []
        for name, width, height in self._THUMBNAIL_RES:
            thumbnails.append({
                'id': name,
                'url': 'http://static.filmon.com/assets/channels/%s/%s.png' % (channel_id, name),
                'width': width,
                'height': height,
            })

        return {
            'id': channel_id,
            'display_id': channel_data.get('alias'),
            'title': self._live_title(title) if is_live else title,
            'description': channel_data.get('description'),
            'thumbnails': thumbnails,
            'formats': formats,
            'is_live': is_live,
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor

class FilmwebIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?filmweb\.no/(?P<type>trailere|filmnytt)/article(?P<id>\d+)\.ece'
    _TEST = {
        'url': 'http://www.filmweb.no/trailere/article1264921.ece',
        'md5': 'e353f47df98e557d67edaceda9dece89',
        'info_dict': {
            'id': '13033574',
            'ext': 'mp4',
            'title': 'Det som en gang var',
            'upload_date': '20160316',
            'timestamp': 1458140101,
            'uploader_id': '12639966',
            'uploader': 'Live Roaldset',
        }
    }

    def _real_extract(self, url):
        article_type, article_id = re.match(self._VALID_URL, url).groups()
        if article_type == 'filmnytt':
            webpage = self._download_webpage(url, article_id)
            article_id = self._search_regex(r'data-videoid="(\d+)"', webpage, 'article id')
        embed_code = self._download_json(
            'https://www.filmweb.no/template_v2/ajax/json_trailerEmbed.jsp',
            article_id, query={
                'articleId': article_id,
            })['embedCode']
        iframe_url = self._proto_relative_url(self._search_regex(
            r'<iframe[^>]+src="([^"]+)', embed_code, 'iframe url'))

        return {
            '_type': 'url_transparent',
            'id': article_id,
            'url': iframe_url,
            'ie_key': 'TwentyThreeVideo',
        }

repo_name: rrosajp/youtube


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

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    int_or_none,
    qualities,
    unified_strdate,
    url_or_none,
)

class FirstTVIE(InfoExtractor):
    IE_NAME = '1tv'
    IE_DESC = 'Первый канал'
    _VALID_URL = r'https?://(?:www\.)?1tv\.ru/(?:[^/]+/)+(?P<id>[^/?#]+)'

    _TESTS = [{
        # single format
        'url': 'http://www.1tv.ru/shows/naedine-so-vsemi/vypuski/gost-lyudmila-senchina-naedine-so-vsemi-vypusk-ot-12-02-2015',
        'md5': 'a1b6b60d530ebcf8daacf4565762bbaf',
        'info_dict': {
            'id': '40049',
            'ext': 'mp4',
            'title': 'Гость Людмила Сенчина. Наедине со всеми. Выпуск от 12.02.2015',
            'thumbnail': r're:^https?://.*\.(?:jpg|JPG)$',
            'upload_date': '20150212',
            'duration': 2694,
        },
    }, {
        # multiple formats
        'url': 'http://www.1tv.ru/shows/dobroe-utro/pro-zdorove/vesennyaya-allergiya-dobroe-utro-fragment-vypuska-ot-07042016',
        'info_dict': {
            'id': '364746',
            'ext': 'mp4',
            'title': 'Весенняя аллергия. Доброе утро. Фрагмент выпуска от 07.04.2016',
            'thumbnail': r're:^https?://.*\.(?:jpg|JPG)$',
            'upload_date': '20160407',
            'duration': 179,
            'formats': 'mincount:3',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://www.1tv.ru/news/issue/2016-12-01/14:00',
        'info_dict': {
            'id': '14:00',
            'title': 'Выпуск новостей в 14:00   1 декабря 2016 года. Новости. Первый канал',
            'description': 'md5:2e921b948f8c1ff93901da78ebdb1dfd',
        },
        'playlist_count': 13,
    }, {
        'url': 'http://www.1tv.ru/shows/tochvtoch-supersezon/vystupleniya/evgeniy-dyatlov-vladimir-vysockiy-koni-priveredlivye-toch-v-toch-supersezon-fragment-vypuska-ot-06-11-2016',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)
        playlist_url = compat_urlparse.urljoin(url, self._search_regex(
            r'data-playlist-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
            webpage, 'playlist url', group='url'))

        parsed_url = compat_urlparse.urlparse(playlist_url)
        qs = compat_urlparse.parse_qs(parsed_url.query)
        item_ids = qs.get('videos_ids[]') or qs.get('news_ids[]')

        items = self._download_json(playlist_url, display_id)

        if item_ids:
            items = [
                item for item in items
                if item.get('uid') and compat_str(item['uid']) in item_ids]
        else:
            items = [items[0]]

        entries = []
        QUALITIES = ('ld', 'sd', 'hd', )

        for item in items:
            title = item['title']
            quality = qualities(QUALITIES)
            formats = []
            path = None
            for f in item.get('mbr', []):
                src = url_or_none(f.get('src'))
                if not src:
                    continue
                tbr = int_or_none(self._search_regex(
                    r'_(\d{3,})\.mp4', src, 'tbr', default=None))
                if not path:
                    path = self._search_regex(
                        r'//[^/]+/(.+?)_\d+\.mp4', src,
                        'm3u8 path', default=None)
                formats.append({
                    'url': src,
                    'format_id': f.get('name'),
                    'tbr': tbr,
                    'source_preference': quality(f.get('name')),
                    # quality metadata of http formats may be incorrect
                    'preference': -1,
                })
            # m3u8 URL format is reverse engineered from [1] (search for
            # master.m3u8). dashEdges (that is currently balancer-vod.1tv.ru)
            # is taken from [2].
            # 1. http://static.1tv.ru/player/eump1tv-current/eump-1tv.all.min.js?rnd=9097422834:formatted
            # 2. http://static.1tv.ru/player/eump1tv-config/config-main.js?rnd=9097422834
            if not path and len(formats) == 1:
                path = self._search_regex(
                    r'//[^/]+/(.+?$)', formats[0]['url'],
                    'm3u8 path', default=None)
            if path:
                if len(formats) == 1:
                    m3u8_path = ','
                else:
                    tbrs = [compat_str(t) for t in sorted(f['tbr'] for f in formats)]
                    m3u8_path = '_,%s,%s' % (','.join(tbrs), '.mp4')
                formats.extend(self._extract_m3u8_formats(
                    'http://balancer-vod.1tv.ru/%s%s.urlset/master.m3u8'
                    % (path, m3u8_path),
                    display_id, 'mp4',
                    entry_protocol='m3u8_native', m3u8_id='hls', fatal=False))
            self._sort_formats(formats)

            thumbnail = item.get('poster') or self._og_search_thumbnail(webpage)
            duration = int_or_none(item.get('duration') or self._html_search_meta(
                'video:duration', webpage, 'video duration', fatal=False))
            upload_date = unified_strdate(self._html_search_meta(
                'ya:ovs:upload_date', webpage, 'upload date', default=None))

            entries.append({
                'id': compat_str(item.get('id') or item['uid']),
                'thumbnail': thumbnail,
                'title': title,
                'upload_date': upload_date,
                'duration': int_or_none(duration),
                'formats': formats
            })

        title = self._html_search_regex(
            (r'
\s*
([^<]*)',
             r"'title'\s*:\s*'([^']+)'"),
            webpage, 'title', default=None) or self._og_search_title(
            webpage, default=None)
        description = self._html_search_regex(
            r'
\s*

\s*
([^<]*)

',
            webpage, 'description', default=None) or self._html_search_meta(
            'description', webpage, 'description', default=None)

        return self.playlist_result(entries, display_id, title, description)


code:
from __future__ import unicode_literals

from .common import InfoExtractor

class FiveMinIE(InfoExtractor):
    IE_NAME = '5min'
    _VALID_URL = r'(?:5min:|https?://(?:[^/]*?5min\.com/|delivery\.vidible\.tv/aol)(?:(?:Scripts/PlayerSeed\.js|playerseed/?)?\?.*?playList=)?)(?P<id>\d+)'

    _TESTS = [
        {
            # From http://www.engadget.com/2013/11/15/ipad-mini-retina-display-review/
            'url': 'http://pshared.5min.com/Scripts/PlayerSeed.js?sid=281&width=560&height=345&playList=518013791',
            'md5': '4f7b0b79bf1a470e5004f7112385941d',
            'info_dict': {
                'id': '518013791',
                'ext': 'mp4',
                'title': 'iPad Mini with Retina Display Review',
                'description': 'iPad mini with Retina Display review',
                'duration': 177,
                'uploader': 'engadget',
                'upload_date': '20131115',
                'timestamp': 1384515288,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            }
        },
        {
            # From http://on.aol.com/video/how-to-make-a-next-level-fruit-salad-518086247
            'url': '5min:518086247',
            'md5': 'e539a9dd682c288ef5a498898009f69e',
            'info_dict': {
                'id': '518086247',
                'ext': 'mp4',
                'title': 'How to Make a Next-Level Fruit Salad',
                'duration': 184,
            },
            'skip': 'no longer available',
        },
        {
            'url': 'http://embed.5min.com/518726732/',
            'only_matching': True,
        },
        {
            'url': 'http://delivery.vidible.tv/aol?playList=518013791',
            'only_matching': True,
        }
    ]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.url_result('aol-video:%s' % video_id)


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

import re

from .common import InfoExtractor
from ..utils import int_or_none

class FiveTVIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.)?5-tv\.ru/
                        (?:
                            (?:[^/]+/)+(?P<id>\d+)|
                            (?P<path>[^/?#]+)(?:[/?#])?
                        )
                    '''

    _TESTS = [{
        'url': 'http://5-tv.ru/news/96814/',
        'md5': 'bbff554ad415ecf5416a2f48c22d9283',
        'info_dict': {
            'id': '96814',
            'ext': 'mp4',
            'title': 'Россияне выбрали имя для общенациональной платежной системы',
            'description': 'md5:a8aa13e2b7ad36789e9f77a74b6de660',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 180,
        },
    }, {
        'url': 'http://5-tv.ru/video/1021729/',
        'info_dict': {
            'id': '1021729',
            'ext': 'mp4',
            'title': '3D принтер',
            'description': 'md5:d76c736d29ef7ec5c0cf7d7c65ffcb41',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 180,
        },
    }, {
        # redirect to https://www.5-tv.ru/projects/1000095/izvestia-glavnoe/
        'url': 'http://www.5-tv.ru/glavnoe/#itemDetails',
        'info_dict': {
            'id': 'glavnoe',
            'ext': 'mp4',
            'title': r're:^Итоги недели с \d+ по \d+ \w+ \d{4} года$',
            'thumbnail': r're:^https?://.*\.jpg$',
        },
        'skip': 'redirect to «Известия. Главное» project page',
    }, {
        'url': 'http://www.5-tv.ru/glavnoe/broadcasts/508645/',
        'only_matching': True,
    }, {
        'url': 'http://5-tv.ru/films/1507502/',
        'only_matching': True,
    }, {
        'url': 'http://5-tv.ru/programs/broadcast/508713/',
        'only_matching': True,
    }, {
        'url': 'http://5-tv.ru/angel/',
        'only_matching': True,
    }, {
        'url': 'http://www.5-tv.ru/schedule/?iframe=true&width=900&height=450',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        video_url = self._search_regex(
            [r'<div[^>]+?class="(?:flow)?player[^>]+?data-href="([^"]+)"',
             r'<a[^>]+?href="([^"]+)"[^>]+?class="videoplayer"'],
            webpage, 'video url')

        title = self._og_search_title(webpage, default=None) or self._search_regex(
            r'<title>([^<]+)</title>', webpage, 'title')
        duration = int_or_none(self._og_search_property(
            'video:duration', webpage, 'duration', default=None))

        return {
            'id': video_id,
            'url': video_url,
            'title': title,
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'duration': duration,
        }


code:
from __future__ import unicode_literals

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

class FlickrIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.|secure\.)?flickr\.com/photos/[\w\-_@]+/(?P<id>\d+)'
    _TEST = {
        'url': 'http://www.flickr.com/photos/forestwander-nature-pictures/5645318632/in/photostream/',
        'md5': '164fe3fa6c22e18d448d4d5af2330f31',
        'info_dict': {
            'id': '5645318632',
            'ext': 'mpg',
            'description': 'Waterfalls in the Springtime at Dark Hollow Waterfalls. These are located just off of Skyline Drive in Virginia. They are only about 6/10 of a mile hike but it is a pretty steep hill and a good climb back up.',
            'title': 'Dark Hollow Waterfalls',
            'duration': 19,
            'timestamp': 1303528740,
            'upload_date': '20110423',
            'uploader_id': '10922353@N03',
            'uploader': 'Forest Wander',
            'uploader_url': 'https://www.flickr.com/photos/forestwander-nature-pictures/',
            'comment_count': int,
            'view_count': int,
            'tags': list,
            'license': 'Attribution-ShareAlike',
        }
    }
    _API_BASE_URL = 'https://api.flickr.com/services/rest?'
    # https://help.yahoo.com/kb/flickr/SLN25525.html
    _LICENSES = {
        '0': 'All Rights Reserved',
        '1': 'Attribution-NonCommercial-ShareAlike',
        '2': 'Attribution-NonCommercial',
        '3': 'Attribution-NonCommercial-NoDerivs',
        '4': 'Attribution',
        '5': 'Attribution-ShareAlike',
        '6': 'Attribution-NoDerivs',
        '7': 'No known copyright restrictions',
        '8': 'United States government work',
        '9': 'Public Domain Dedication (CC0)',
        '10': 'Public Domain Work',
    }

    def _call_api(self, method, video_id, api_key, note, secret=None):
        query = {
            'photo_id': video_id,
            'method': 'flickr.%s' % method,
            'api_key': api_key,
            'format': 'json',
            'nojsoncallback': 1,
        }
        if secret:
            query['secret'] = secret
        data = self._download_json(self._API_BASE_URL + compat_urllib_parse_urlencode(query), video_id, note)
        if data['stat'] != 'ok':
            raise ExtractorError(data['message'])
        return data

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

        api_key = self._download_json(
            'https://www.flickr.com/hermes_error_beacon.gne', video_id,
            'Downloading api key')['site_key']

        video_info = self._call_api(
            'photos.getInfo', video_id, api_key, 'Downloading video info')['photo']
        if video_info['media'] == 'video':
            streams = self._call_api(
                'video.getStreamInfo', video_id, api_key,
                'Downloading streams info', video_info['secret'])['streams']

            preference = qualities(
                ['288p', 'iphone_wifi', '100', '300', '700', '360p', 'appletv', '720p', '1080p', 'orig'])

            formats = []
            for stream in streams['stream']:
                stream_type = compat_str(stream.get('type'))
                formats.append({
                    'format_id': stream_type,
                    'url': stream['_content'],
                    'preference': preference(stream_type),
                })
            self._sort_formats(formats)

            owner = video_info.get('owner', {})
            uploader_id = owner.get('nsid')
            uploader_path = owner.get('path_alias') or uploader_id
            uploader_url = 'https://www.flickr.com/photos/%s/' % uploader_path if uploader_path else None

            return {
                'id': video_id,
                'title': video_info['title']['_content'],
                'description': video_info.get('description', {}).get('_content'),
                'formats': formats,
                'timestamp': int_or_none(video_info.get('dateuploaded')),
                'duration': int_or_none(video_info.get('video', {}).get('duration')),
                'uploader_id': uploader_id,
                'uploader': owner.get('realname'),
                'uploader_url': uploader_url,
                'comment_count': int_or_none(video_info.get('comments', {}).get('_content')),
                'view_count': int_or_none(video_info.get('views')),
                'tags': [tag.get('_content') for tag in video_info.get('tags', {}).get('tag', [])],
                'license': self._LICENSES.get(video_info.get('license')),
            }
        else:
            raise ExtractorError('not a video', expected=True)


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

from .common import InfoExtractor
from ..compat import compat_parse_qs
from ..utils import (
    int_or_none,
    parse_duration,
    parse_iso8601,
    xpath_text,
)

class FolketingetIE(InfoExtractor):
    IE_DESC = 'Folketinget (ft.dk; Danish parliament)'
    _VALID_URL = r'https?://(?:www\.)?ft\.dk/webtv/video/[^?#]*?\.(?P<id>[0-9]+)\.aspx'
    _TEST = {
        'url': 'http://www.ft.dk/webtv/video/20141/eru/td.1165642.aspx?as=1#player',
        'md5': '6269e8626fa1a891bf5369b386ae996a',
        'info_dict': {
            'id': '1165642',
            'ext': 'mp4',
            'title': 'Åbent samråd i Erhvervsudvalget',
            'description': 'Åbent samråd med erhvervs- og vækstministeren om regeringens politik på teleområdet',
            'view_count': int,
            'width': 768,
            'height': 432,
            'tbr': 928000,
            'timestamp': 1416493800,
            'upload_date': '20141120',
            'duration': 3960,
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
    }

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

        title = self._og_search_title(webpage)
        description = self._html_search_regex(
            r'(?s)<div class="video-item-agenda"[^>]*>(.*?)<',
            webpage, 'description', fatal=False)

        player_params = compat_parse_qs(self._search_regex(
            r'<embed src="http://ft\.arkena\.tv/flash/ftplayer\.swf\?([^"]+)"',
            webpage, 'player params'))
        xml_url = player_params['xml'][0]
        doc = self._download_xml(xml_url, video_id)

        timestamp = parse_iso8601(xpath_text(doc, './/date'))
        duration = parse_duration(xpath_text(doc, './/duration'))
        width = int_or_none(xpath_text(doc, './/width'))
        height = int_or_none(xpath_text(doc, './/height'))
        view_count = int_or_none(xpath_text(doc, './/views'))

        formats = [{
            'format_id': n.attrib['bitrate'],
            'url': xpath_text(n, './url', fatal=True),
            'tbr': int_or_none(n.attrib['bitrate']),
        } for n in doc.findall('.//streams/stream')]
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': description,
            'timestamp': timestamp,
            'width': width,
            'height': height,
            'duration': duration,
            'view_count': view_count,
        }


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

from .common import InfoExtractor
from .streamable import StreamableIE

class FootyRoomIE(InfoExtractor):
    _VALID_URL = r'https?://footyroom\.com/matches/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://footyroom.com/matches/79922154/hull-city-vs-chelsea/review',
        'info_dict': {
            'id': '79922154',
            'title': 'VIDEO Hull City 0 - 2 Chelsea',
        },
        'playlist_count': 2,
        'add_ie': [StreamableIE.ie_key()],
    }, {
        'url': 'http://footyroom.com/matches/75817984/georgia-vs-germany/review',
        'info_dict': {
            'id': '75817984',
            'title': 'VIDEO Georgia 0 - 2 Germany',
        },
        'playlist_count': 1,
        'add_ie': ['Playwire']
    }]

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

        webpage = self._download_webpage(url, playlist_id)

        playlist = self._parse_json(self._search_regex(
            r'DataStore\.media\s*=\s*([^;]+)', webpage, 'media data'),
            playlist_id)

        playlist_title = self._og_search_title(webpage)

        entries = []
        for video in playlist:
            payload = video.get('payload')
            if not payload:
                continue
            playwire_url = self._html_search_regex(
                r'data-config="([^"]+)"', payload,
                'playwire url', default=None)
            if playwire_url:
                entries.append(self.url_result(self._proto_relative_url(
                    playwire_url, 'http:'), 'Playwire'))

            streamable_url = StreamableIE._extract_url(payload)
            if streamable_url:
                entries.append(self.url_result(
                    streamable_url, StreamableIE.ie_key()))

        return self.playlist_result(entries, playlist_id, playlist_title)


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

from .common import InfoExtractor

class Formula1IE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?formula1\.com/(?:content/fom-website/)?en/video/\d{4}/\d{1,2}/(?P<id>.+?)\.html'
    _TESTS = [{
        'url': 'http://www.formula1.com/content/fom-website/en/video/2016/5/Race_highlights_-_Spain_2016.html',
        'md5': '8c79e54be72078b26b89e0e111c0502b',
        'info_dict': {
            'id': 'JvYXJpMzE6pArfHWm5ARp5AiUmD-gibV',
            'ext': 'mp4',
            'title': 'Race highlights - Spain 2016',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'add_ie': ['Ooyala'],
    }, {
        'url': 'http://www.formula1.com/en/video/2016/5/Race_highlights_-_Spain_2016.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        ooyala_embed_code = self._search_regex(
            r'data-videoid="([^"]+)"', webpage, 'ooyala embed code')
        return self.url_result(
            'ooyala:%s' % ooyala_embed_code, 'Ooyala', ooyala_embed_code)


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_b64decode,
    compat_str,
    compat_urllib_parse_unquote,
    compat_urlparse,
)
from ..utils import (
    int_or_none,
    parse_duration,
    parse_iso8601,
    str_or_none,
    str_to_int,
    try_get,
    unified_timestamp,
    url_or_none,
)

class FourTubeBaseIE(InfoExtractor):
    def _extract_formats(self, url, video_id, media_id, sources):
        token_url = 'https://%s/%s/desktop/%s' % (
            self._TKN_HOST, media_id, '+'.join(sources))

        parsed_url = compat_urlparse.urlparse(url)
        tokens = self._download_json(token_url, video_id, data=b'', headers={
            'Origin': '%s://%s' % (parsed_url.scheme, parsed_url.hostname),
            'Referer': url,
        })
        formats = [{
            'url': tokens[format]['token'],
            'format_id': format + 'p',
            'resolution': format + 'p',
            'quality': int(format),
        } for format in sources]
        self._sort_formats(formats)
        return formats

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

        if kind == 'm' or not display_id:
            url = self._URL_TEMPLATE % video_id

        webpage = self._download_webpage(url, video_id)

        title = self._html_search_meta('name', webpage)
        timestamp = parse_iso8601(self._html_search_meta(
            'uploadDate', webpage))
        thumbnail = self._html_search_meta('thumbnailUrl', webpage)
        uploader_id = self._html_search_regex(
            r'',
            webpage, 'uploader id', fatal=False)
        uploader = self._html_search_regex(
            r'',
            webpage, 'uploader', fatal=False)

        categories_html = self._search_regex(
            r'(?s)>\s*Categories / Tags\s*.*?
(.*?)
',
            webpage, 'categories', fatal=False)
        categories = None
        if categories_html:
            categories = [
                c.strip() for c in re.findall(
                    r'(?s)
(.*?)', categories_html)]

        view_count = str_to_int(self._search_regex(
            r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([0-9,]+)">',
            webpage, 'view count', default=None))
        like_count = str_to_int(self._search_regex(
            r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserLikes:([0-9,]+)">',
            webpage, 'like count', default=None))
        duration = parse_duration(self._html_search_meta('duration', webpage))

        media_id = self._search_regex(
            r'<button[^>]+data-id=(["\'])(?P<id>\d+)\1[^>]+data-quality=', webpage,
            'media id', default=None, group='id')
        sources = [
            quality
            for _, quality in re.findall(r'<button[^>]+data-quality=(["\'])(.+?)\1', webpage)]
        if not (media_id and sources):
            player_js = self._download_webpage(
                self._search_regex(
                    r'


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

import json
import uuid

from .adobepass import AdobePassIE
from ..compat import (
    compat_HTTPError,
    compat_str,
    compat_urllib_parse_unquote,
)
from ..utils import (
    ExtractorError,
    int_or_none,
    parse_age_limit,
    parse_duration,
    try_get,
    unified_timestamp,
)

class FOXIE(AdobePassIE):
    _VALID_URL = r'https?://(?:www\.)?fox\.com/watch/(?P<id>[\da-fA-F]+)'
    _TESTS = [{
        # clip
        'url': 'https://www.fox.com/watch/4b765a60490325103ea69888fb2bd4e8/',
        'md5': 'ebd296fcc41dd4b19f8115d8461a3165',
        'info_dict': {
            'id': '4b765a60490325103ea69888fb2bd4e8',
            'ext': 'mp4',
            'title': 'Aftermath: Bruce Wayne Develops Into The Dark Knight',
            'description': 'md5:549cd9c70d413adb32ce2a779b53b486',
            'duration': 102,
            'timestamp': 1504291893,
            'upload_date': '20170901',
            'creator': 'FOX',
            'series': 'Gotham',
            'age_limit': 14,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # episode, geo-restricted
        'url': 'https://www.fox.com/watch/087036ca7f33c8eb79b08152b4dd75c1/',
        'only_matching': True,
    }, {
        # episode, geo-restricted, tv provided required
        'url': 'https://www.fox.com/watch/30056b295fb57f7452aeeb4920bc3024/',
        'only_matching': True,
    }]
    _GEO_BYPASS = False
    _HOME_PAGE_URL = 'https://www.fox.com/'
    _API_KEY = 'abdcbed02c124d393b39e818a4312055'
    _access_token = None

    def _call_api(self, path, video_id, data=None):
        headers = {
            'X-Api-Key': self._API_KEY,
        }
        if self._access_token:
            headers['Authorization'] = 'Bearer ' + self._access_token
        try:
            return self._download_json(
                'https://api2.fox.com/v2.0/' + path,
                video_id, data=data, headers=headers)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                entitlement_issues = self._parse_json(
                    e.cause.read().decode(), video_id)['entitlementIssues']
                for e in entitlement_issues:
                    if e.get('errorCode') == 1005:
                        raise ExtractorError(
                            'This video is only available via cable service provider '
                            'subscription. You may want to use --cookies.', expected=True)
                messages = ', '.join([e['message'] for e in entitlement_issues])
                raise ExtractorError(messages, expected=True)
            raise

    def _real_initialize(self):
        if not self._access_token:
            mvpd_auth = self._get_cookies(self._HOME_PAGE_URL).get('mvpd-auth')
            if mvpd_auth:
                self._access_token = (self._parse_json(compat_urllib_parse_unquote(
                    mvpd_auth.value), None, fatal=False) or {}).get('accessToken')
            if not self._access_token:
                self._access_token = self._call_api(
                    'login', None, json.dumps({
                        'deviceId': compat_str(uuid.uuid4()),
                    }).encode())['accessToken']

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

        video = self._call_api('vodplayer/' + video_id, video_id)

        title = video['name']
        release_url = video['url']
        try:
            m3u8_url = self._download_json(release_url, video_id)['playURL']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                error = self._parse_json(e.cause.read().decode(), video_id)
                if error.get('exception') == 'GeoLocationBlocked':
                    self.raise_geo_restricted(countries=['US'])
                raise ExtractorError(error['description'], expected=True)
            raise
        formats = self._extract_m3u8_formats(
            m3u8_url, video_id, 'mp4',
            entry_protocol='m3u8_native', m3u8_id='hls')
        self._sort_formats(formats)

        data = try_get(
            video, lambda x: x['trackingData']['properties'], dict) or {}

        duration = int_or_none(video.get('durationInSeconds')) or int_or_none(
            video.get('duration')) or parse_duration(video.get('duration'))
        timestamp = unified_timestamp(video.get('datePublished'))
        creator = data.get('brand') or data.get('network') or video.get('network')
        series = video.get('seriesName') or data.get(
            'seriesName') or data.get('show')

        subtitles = {}
        for doc_rel in video.get('documentReleases', []):
            rel_url = doc_rel.get('url')
            if not url or doc_rel.get('format') != 'SCC':
                continue
            subtitles['en'] = [{
                'url': rel_url,
                'ext': 'scc',
            }]
            break

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': video.get('description'),
            'duration': duration,
            'timestamp': timestamp,
            'age_limit': parse_age_limit(video.get('contentRating')),
            'creator': creator,
            'series': series,
            'season_number': int_or_none(video.get('seasonNumber')),
            'episode': video.get('name'),
            'episode_number': int_or_none(video.get('episodeNumber')),
            'release_year': int_or_none(video.get('releaseYear')),
            'subtitles': subtitles,
        }


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

from .common import InfoExtractor

class FOX9IE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?fox9\.com/video/(?P<id>\d+)'

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.url_result(
            'anvato:anvato_epfox_app_web_prod_b3373168e12f423f41504f207000188daf88251b:' + video_id,
            'Anvato', video_id)

class FOX9NewsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?fox9\.com/news/(?P<id>[^/?&#]+)'
    _TEST = {
        'url': 'https://www.fox9.com/news/black-bear-in-tree-draws-crowd-in-downtown-duluth-minnesota',
        'md5': 'd6e1b2572c3bab8a849c9103615dd243',
        'info_dict': {
            'id': '314473',
            'ext': 'mp4',
            'title': 'Bear climbs tree in downtown Duluth',
            'description': 'md5:6a36bfb5073a411758a752455408ac90',
            'duration': 51,
            'timestamp': 1478123580,
            'upload_date': '20161102',
            'uploader': 'EPFOX',
            'categories': ['News', 'Sports'],
            'tags': ['news', 'video'],
        },
    }

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        anvato_id = self._search_regex(
            r'anvatoId\s*:\s*[\'"](\d+)', webpage, 'anvato id')
        return self.url_result('https://www.fox9.com/video/' + anvato_id, 'FOX9')


code:
from __future__ import unicode_literals

import itertools

from .common import InfoExtractor
from ..utils import (
    get_element_by_id,
    int_or_none,
    remove_end,
)

class FoxgayIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?foxgay\.com/videos/(?:\S+-)?(?P<id>\d+)\.shtml'
    _TEST = {
        'url': 'http://foxgay.com/videos/fuck-turkish-style-2582.shtml',
        'md5': '344558ccfea74d33b7adbce22e577f54',
        'info_dict': {
            'id': '2582',
            'ext': 'mp4',
            'title': 'Fuck Turkish-style',
            'description': 'md5:6ae2d9486921891efe89231ace13ffdf',
            'age_limit': 18,
            'thumbnail': r're:https?://.*\.jpg$',
        },
    }

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

        title = remove_end(self._html_search_regex(
            r'<title>([^<]+)</title>', webpage, 'title'), ' - Foxgay.com')
        description = get_element_by_id('inf_tit', webpage)

        # The default user-agent with foxgay cookies leads to pages without videos
        self._downloader.cookiejar.clear('.foxgay.com')
        # Find the URL for the iFrame which contains the actual video.
        iframe_url = self._html_search_regex(
            r'<iframe[^>]+src=([\'"])(?P<url>[^\'"]+)\1', webpage,
            'video frame', group='url')
        iframe = self._download_webpage(
            iframe_url, video_id, headers={'User-Agent': 'curl/7.50.1'},
            note='Downloading video frame')
        video_data = self._parse_json(self._search_regex(
            r'video_data\s*=\s*([^;]+);', iframe, 'video data'), video_id)

        formats = [{
            'url': source,
            'height': int_or_none(resolution),
        } for source, resolution in zip(
            video_data['sources'], video_data.get('resolutions', itertools.repeat(None)))]

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': description,
            'thumbnail': video_data.get('act_vid', {}).get('thumb'),
            'age_limit': 18,
        }


code:
from __future__ import unicode_literals

import re

from .amp import AMPIE
from .common import InfoExtractor

class FoxNewsIE(AMPIE):
    IE_NAME = 'foxnews'
    IE_DESC = 'Fox News and Fox Business Video'
    _VALID_URL = r'https?://(?P<host>video\.(?:insider\.)?fox(?:news|business)\.com)/v/(?:video-embed\.html\?video_id=)?(?P<id>\d+)'
    _TESTS = [
        {
            'url': 'http://video.foxnews.com/v/3937480/frozen-in-time/#sp=show-clips',
            'md5': '32aaded6ba3ef0d1c04e238d01031e5e',
            'info_dict': {
                'id': '3937480',
                'ext': 'flv',
                'title': 'Frozen in Time',
                'description': '16-year-old girl is size of toddler',
                'duration': 265,
                'timestamp': 1304411491,
                'upload_date': '20110503',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'http://video.foxnews.com/v/3922535568001/rep-luis-gutierrez-on-if-obamas-immigration-plan-is-legal/#sp=show-clips',
            'md5': '5846c64a1ea05ec78175421b8323e2df',
            'info_dict': {
                'id': '3922535568001',
                'ext': 'mp4',
                'title': "Rep. Luis Gutierrez on if Obama's immigration plan is legal",
                'description': "Congressman discusses president's plan",
                'duration': 292,
                'timestamp': 1417662047,
                'upload_date': '20141204',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'http://video.foxnews.com/v/video-embed.html?video_id=3937480&d=video.foxnews.com',
            'only_matching': True,
        },
        {
            'url': 'http://video.foxbusiness.com/v/4442309889001',
            'only_matching': True,
        },
        {
            # From http://insider.foxnews.com/2016/08/25/univ-wisconsin-student-group-pushing-silence-certain-words
            'url': 'http://video.insider.foxnews.com/v/video-embed.html?video_id=5099377331001&autoplay=true&share_url=http://insider.foxnews.com/2016/08/25/univ-wisconsin-student-group-pushing-silence-certain-words&share_title=Student%20Group:%20Saying%20%27Politically%20Correct,%27%20%27Trash%27%20and%20%27Lame%27%20Is%20Offensive&share=true',
            'only_matching': True,
        },
    ]

    @staticmethod
    def _extract_urls(webpage):
        return [
            mobj.group('url')
            for mobj in re.finditer(
                r'<(?:amp-)?iframe[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//video\.foxnews\.com/v/video-embed\.html?.*?\bvideo_id=\d+.*?)\1',
                webpage)]

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

        info = self._extract_feed_info(
            'http://%s/v/feed/video/%s.js?template=fox' % (host, video_id))
        info['id'] = video_id
        return info

class FoxNewsArticleIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:insider\.)?foxnews\.com/(?!v)([^/]+/)+(?P<id>[a-z-]+)'
    IE_NAME = 'foxnews:article'

    _TESTS = [{
        # data-video-id
        'url': 'http://www.foxnews.com/politics/2016/09/08/buzz-about-bud-clinton-camp-denies-claims-wore-earpiece-at-forum.html',
        'md5': '83d44e1aff1433e7a29a7b537d1700b5',
        'info_dict': {
            'id': '5116295019001',
            'ext': 'mp4',
            'title': 'Trump and Clinton asked to defend positions on Iraq War',
            'description': 'Veterans react on \'The Kelly File\'',
            'timestamp': 1473301045,
            'upload_date': '20160908',
        },
    }, {
        # iframe embed
        'url': 'http://www.foxnews.com/us/2018/03/09/parkland-survivor-kyle-kashuv-on-meeting-trump-his-app-to-prevent-another-school-shooting.amp.html?__twitter_impression=true',
        'info_dict': {
            'id': '5748266721001',
            'ext': 'flv',
            'title': 'Kyle Kashuv has a positive message for the Trump White House',
            'description': 'Marjory Stoneman Douglas student disagrees with classmates.',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 229,
            'timestamp': 1520594670,
            'upload_date': '20180309',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://insider.foxnews.com/2016/08/25/univ-wisconsin-student-group-pushing-silence-certain-words',
        'only_matching': True,
    }]

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

        video_id = self._html_search_regex(
            r'data-video-id=([\'"])(?P<id>[^\'"]+)\1',
            webpage, 'video ID', group='id', default=None)
        if video_id:
            return self.url_result(
                'http://video.foxnews.com/v/' + video_id, FoxNewsIE.ie_key())

        return self.url_result(
            FoxNewsIE._extract_urls(webpage)[0], FoxNewsIE.ie_key())


code:
from __future__ import unicode_literals

from .common import InfoExtractor

class FoxSportsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?foxsports\.com/(?:[^/]+/)*video/(?P<id>\d+)'

    _TEST = {
        'url': 'http://www.foxsports.com/tennessee/video/432609859715',
        'md5': 'b49050e955bebe32c301972e4012ac17',
        'info_dict': {
            'id': '432609859715',
            'ext': 'mp4',
            'title': 'Courtney Lee on going up 2-0 in series vs. Blazers',
            'description': 'Courtney Lee talks about Memphis being focused.',
            # TODO: fix timestamp
            'upload_date': '19700101',  # '20150423',
            # 'timestamp': 1429761109,
            'uploader': 'NEWA-FNG-FOXSPORTS',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'add_ie': ['ThePlatform'],
    }

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

        return self.url_result(
            'https://feed.theplatform.com/f/BKQ29B/foxsports-all?byId=' + video_id, 'ThePlatformFeed')


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

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    extract_attributes,
    int_or_none,
)

class FranceCultureIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?franceculture\.fr/emissions/(?:[^/]+/)*(?P<id>[^/?#&]+)'
    _TEST = {
        'url': 'http://www.franceculture.fr/emissions/carnet-nomade/rendez-vous-au-pays-des-geeks',
        'info_dict': {
            'id': 'rendez-vous-au-pays-des-geeks',
            'display_id': 'rendez-vous-au-pays-des-geeks',
            'ext': 'mp3',
            'title': 'Rendez-vous au pays des geeks',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20140301',
            'timestamp': 1393642916,
            'vcodec': 'none',
        }
    }

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

        webpage = self._download_webpage(url, display_id)

        video_data = extract_attributes(self._search_regex(
            r'''(?sx)
                (?:

|

]+class="[^"]*?(?:title-zone-diffusion|heading-zone-(?:wrapper|player-button))[^"]*?"[^>]*>
                ).*?
                (<button[^>]+data-asset-source="[^"]+"[^>]+>)
            ''',
            webpage, 'video data'))

        video_url = video_data['data-asset-source']
        title = video_data.get('data-asset-title') or self._og_search_title(webpage)

        description = self._html_search_regex(
            r'(?s)
]+class="intro"[^>]*>.*?
(.+?)
',
            webpage, 'description', default=None)
        thumbnail = self._search_regex(
            r'(?s)
]+itemtype="https://schema.org/ImageObject"[^>]*>.*?]+(?:data-dejavu-)?src="([^"]+)"',
            webpage, 'thumbnail', fatal=False)
        uploader = self._html_search_regex(
            r'(?s)(.*?)',
            webpage, 'uploader', default=None)
        ext = determine_ext(video_url.lower())

        return {
            'id': display_id,
            'display_id': display_id,
            'url': video_url,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'ext': ext,
            'vcodec': 'none' if ext == 'mp3' else None,
            'uploader': uploader,
            'timestamp': int_or_none(video_data.get('data-asset-created-date')),
            'duration': int_or_none(video_data.get('data-duration')),
        }


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

from .common import InfoExtractor
from ..utils import month_by_name

class FranceInterIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?franceinter\.fr/emissions/(?P<id>[^?#]+)'

    _TEST = {
        'url': 'https://www.franceinter.fr/emissions/affaires-sensibles/affaires-sensibles-07-septembre-2016',
        'md5': '9e54d7bdb6fdc02a841007f8a975c094',
        'info_dict': {
            'id': 'affaires-sensibles/affaires-sensibles-07-septembre-2016',
            'ext': 'mp3',
            'title': 'Affaire Cahuzac : le contentieux du compte en Suisse',
            'description': 'md5:401969c5d318c061f86bda1fa359292b',
            'upload_date': '20160907',
        },
    }

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

        webpage = self._download_webpage(url, video_id)

        video_url = self._search_regex(
            r'(?s)<div[^>]+class=["\']page-diffusion["\'][^>]*>.*?<button[^>]+data-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
            webpage, 'video url', group='url')

        title = self._og_search_title(webpage)
        description = self._og_search_description(webpage)

        upload_date_str = self._search_regex(
            r'class=["\']\s*cover-emission-period\s*["\'][^>]*>[^<]+\s+(\d{1,2}\s+[^\s]+\s+\d{4})<',
            webpage, 'upload date', fatal=False)
        if upload_date_str:
            upload_date_list = upload_date_str.split()
            upload_date_list.reverse()
            upload_date_list[1] = '%02d' % (month_by_name(upload_date_list[1], lang='fr') or 0)
            upload_date_list[2] = '%02d' % int(upload_date_list[2])
            upload_date = ''.join(upload_date_list)
        else:
            upload_date = None

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'upload_date': upload_date,
            'formats': [{
                'url': video_url,
                'vcodec': 'none',
            }],
        }


code:
# coding: utf-8

from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    clean_html,
    determine_ext,
    ExtractorError,
    int_or_none,
    parse_duration,
    try_get,
    url_or_none,
)
from .dailymotion import DailymotionIE

class FranceTVBaseInfoExtractor(InfoExtractor):
    def _make_url_result(self, video_or_full_id, catalog=None):
        full_id = 'francetv:%s' % video_or_full_id
        if '@' not in video_or_full_id and catalog:
            full_id += '@%s' % catalog
        return self.url_result(
            full_id, ie=FranceTVIE.ie_key(),
            video_id=video_or_full_id.split('@')[0])

class FranceTVIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    (?:
                        https?://
                            sivideo\.webservices\.francetelevisions\.fr/tools/getInfosOeuvre/v2/\?
                            .*?\bidDiffusion=[^&]+|
                        (?:
                            https?://videos\.francetv\.fr/video/|
                            francetv:
                        )
                        (?P<id>[^@]+)(?:@(?P<catalog>.+))?
                    )
                    '''

    _TESTS = [{
        # without catalog
        'url': 'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=162311093&callback=_jsonp_loader_callback_request_0',
        'md5': 'c2248a8de38c4e65ea8fae7b5df2d84f',
        'info_dict': {
            'id': '162311093',
            'ext': 'mp4',
            'title': '13h15, le dimanche... - Les mystères de Jésus',
            'description': 'md5:75efe8d4c0a8205e5904498ffe1e1a42',
            'timestamp': 1502623500,
            'upload_date': '20170813',
        },
    }, {
        # with catalog
        'url': 'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=NI_1004933&catalogue=Zouzous&callback=_jsonp_loader_callback_request_4',
        'only_matching': True,
    }, {
        'url': 'http://videos.francetv.fr/video/NI_657393@Regions',
        'only_matching': True,
    }, {
        'url': 'francetv:162311093',
        'only_matching': True,
    }, {
        'url': 'francetv:NI_1004933@Zouzous',
        'only_matching': True,
    }, {
        'url': 'francetv:NI_983319@Info-web',
        'only_matching': True,
    }, {
        'url': 'francetv:NI_983319',
        'only_matching': True,
    }, {
        'url': 'francetv:NI_657393@Regions',
        'only_matching': True,
    }, {
        # france-3 live
        'url': 'francetv:SIM_France3',
        'only_matching': True,
    }]

    def _extract_video(self, video_id, catalogue=None):
        # Videos are identified by idDiffusion so catalogue part is optional.
        # However when provided, some extra formats may be returned so we pass
        # it if available.
        info = self._download_json(
            'https://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/',
            video_id, 'Downloading video JSON', query={
                'idDiffusion': video_id,
                'catalogue': catalogue or '',
            })

        if info.get('status') == 'NOK':
            raise ExtractorError(
                '%s returned error: %s' % (self.IE_NAME, info['message']),
                expected=True)
        allowed_countries = info['videos'][0].get('geoblocage')
        if allowed_countries:
            georestricted = True
            geo_info = self._download_json(
                'http://geo.francetv.fr/ws/edgescape.json', video_id,
                'Downloading geo restriction info')
            country = geo_info['reponse']['geo_info']['country_code']
            if country not in allowed_countries:
                raise ExtractorError(
                    'The video is not available from your location',
                    expected=True)
        else:
            georestricted = False

        def sign(manifest_url, manifest_id):
            for host in ('hdfauthftv-a.akamaihd.net', 'hdfauth.francetv.fr'):
                signed_url = url_or_none(self._download_webpage(
                    'https://%s/esi/TA' % host, video_id,
                    'Downloading signed %s manifest URL' % manifest_id,
                    fatal=False, query={
                        'url': manifest_url,
                    }))
                if signed_url:
                    return signed_url
            return manifest_url

        is_live = None

        formats = []
        for video in info['videos']:
            if video['statut'] != 'ONLINE':
                continue
            video_url = video['url']
            if not video_url:
                continue
            if is_live is None:
                is_live = (try_get(
                    video, lambda x: x['plages_ouverture'][0]['direct'],
                    bool) is True) or '/live.francetv.fr/' in video_url
            format_id = video['format']
            ext = determine_ext(video_url)
            if ext == 'f4m':
                if georestricted:
                    # See https://github.com/ytdl-org/youtube-dl/issues/3963
                    # m3u8 urls work fine
                    continue
                formats.extend(self._extract_f4m_formats(
                    sign(video_url, format_id) + '&hdcore=3.7.0&plugin=aasp-3.7.0.39.44',
                    video_id, f4m_id=format_id, fatal=False))
            elif ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    sign(video_url, format_id), video_id, 'mp4',
                    entry_protocol='m3u8_native', m3u8_id=format_id,
                    fatal=False))
            elif video_url.startswith('rtmp'):
                formats.append({
                    'url': video_url,
                    'format_id': 'rtmp-%s' % format_id,
                    'ext': 'flv',
                })
            else:
                if self._is_valid_url(video_url, video_id, format_id):
                    formats.append({
                        'url': video_url,
                        'format_id': format_id,
                    })
        self._sort_formats(formats)

        title = info['titre']
        subtitle = info.get('sous_titre')
        if subtitle:
            title += ' - %s' % subtitle
        title = title.strip()

        subtitles = {}
        subtitles_list = [{
            'url': subformat['url'],
            'ext': subformat.get('format'),
        } for subformat in info.get('subtitles', []) if subformat.get('url')]
        if subtitles_list:
            subtitles['fr'] = subtitles_list

        return {
            'id': video_id,
            'title': self._live_title(title) if is_live else title,
            'description': clean_html(info['synopsis']),
            'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', info['image']),
            'duration': int_or_none(info.get('real_duration')) or parse_duration(info['duree']),
            'timestamp': int_or_none(info['diffusion']['timestamp']),
            'is_live': is_live,
            'formats': formats,
            'subtitles': subtitles,
        }

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

        if not video_id:
            qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
            video_id = qs.get('idDiffusion', [None])[0]
            catalog = qs.get('catalogue', [None])[0]
            if not video_id:
                raise ExtractorError('Invalid URL', expected=True)

        return self._extract_video(video_id, catalog)

class FranceTVSiteIE(FranceTVBaseInfoExtractor):
    _VALID_URL = r'https?://(?:(?:www\.)?france\.tv|mobile\.france\.tv)/(?:[^/]+/)*(?P<id>[^/]+)\.html'

    _TESTS = [{
        'url': 'https://www.france.tv/france-2/13h15-le-dimanche/140921-les-mysteres-de-jesus.html',
        'info_dict': {
            'id': 'ec217ecc-0733-48cf-ac06-af1347b849d1',
            'ext': 'mp4',
            'title': '13h15, le dimanche... - Les mystères de Jésus',
            'description': 'md5:75efe8d4c0a8205e5904498ffe1e1a42',
            'timestamp': 1502623500,
            'upload_date': '20170813',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [FranceTVIE.ie_key()],
    }, {
        # france3
        'url': 'https://www.france.tv/france-3/des-chiffres-et-des-lettres/139063-emission-du-mardi-9-mai-2017.html',
        'only_matching': True,
    }, {
        # france4
        'url': 'https://www.france.tv/france-4/hero-corp/saison-1/134151-apres-le-calme.html',
        'only_matching': True,
    }, {
        # france5
        'url': 'https://www.france.tv/france-5/c-a-dire/saison-10/137013-c-a-dire.html',
        'only_matching': True,
    }, {
        # franceo
        'url': 'https://www.france.tv/france-o/archipels/132249-mon-ancetre-l-esclave.html',
        'only_matching': True,
    }, {
        # france2 live
        'url': 'https://www.france.tv/france-2/direct.html',
        'only_matching': True,
    }, {
        'url': 'https://www.france.tv/documentaires/histoire/136517-argentine-les-500-bebes-voles-de-la-dictature.html',
        'only_matching': True,
    }, {
        'url': 'https://www.france.tv/jeux-et-divertissements/divertissements/133965-le-web-contre-attaque.html',
        'only_matching': True,
    }, {
        'url': 'https://mobile.france.tv/france-5/c-dans-l-air/137347-emission-du-vendredi-12-mai-2017.html',
        'only_matching': True,
    }, {
        'url': 'https://www.france.tv/142749-rouge-sang.html',
        'only_matching': True,
    }, {
        # france-3 live
        'url': 'https://www.france.tv/france-3/direct.html',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        catalogue = None
        video_id = self._search_regex(
            r'(?:data-main-video\s*=|videoId["\']?\s*[:=])\s*(["\'])(?P<id>(?:(?!\1).)+)\1',
            webpage, 'video id', default=None, group='id')

        if not video_id:
            video_id, catalogue = self._html_search_regex(
                r'(?:href=|player\.setVideo\(\s*)"http://videos?\.francetv\.fr/video/([^@]+@[^"]+)"',
                webpage, 'video ID').split('@')

        return self._make_url_result(video_id, catalogue)

class FranceTVEmbedIE(FranceTVBaseInfoExtractor):
    _VALID_URL = r'https?://embed\.francetv\.fr/*\?.*?\bue=(?P<id>[^&]+)'

    _TESTS = [{
        'url': 'http://embed.francetv.fr/?ue=7fd581a2ccf59d2fc5719c5c13cf6961',
        'info_dict': {
            'id': 'NI_983319',
            'ext': 'mp4',
            'title': 'Le Pen Reims',
            'upload_date': '20170505',
            'timestamp': 1493981780,
            'duration': 16,
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [FranceTVIE.ie_key()],
    }]

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

        video = self._download_json(
            'http://api-embed.webservices.francetelevisions.fr/key/%s' % video_id,
            video_id)

        return self._make_url_result(video['video_id'], video.get('catalog'))

class FranceTVInfoIE(FranceTVBaseInfoExtractor):
    IE_NAME = 'francetvinfo.fr'
    _VALID_URL = r'https?://(?:www|mobile|france3-regions)\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&.]+)'

    _TESTS = [{
        'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
        'info_dict': {
            'id': '84981923',
            'ext': 'mp4',
            'title': 'Soir 3',
            'upload_date': '20130826',
            'timestamp': 1377548400,
            'subtitles': {
                'fr': 'mincount:2',
            },
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [FranceTVIE.ie_key()],
    }, {
        'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
        'only_matching': True,
    }, {
        'url': 'http://www.francetvinfo.fr/economie/entreprises/les-entreprises-familiales-le-secret-de-la-reussite_933271.html',
        'only_matching': True,
    }, {
        'url': 'http://france3-regions.francetvinfo.fr/bretagne/cotes-d-armor/thalassa-echappee-breizh-ce-venredi-dans-les-cotes-d-armor-954961.html',
        'only_matching': True,
    }, {
        # Dailymotion embed
        'url': 'http://www.francetvinfo.fr/politique/notre-dame-des-landes/video-sur-france-inter-cecile-duflot-denonce-le-regard-meprisant-de-patrick-cohen_1520091.html',
        'md5': 'ee7f1828f25a648addc90cb2687b1f12',
        'info_dict': {
            'id': 'x4iiko0',
            'ext': 'mp4',
            'title': 'NDDL, référendum, Brexit : Cécile Duflot répond à Patrick Cohen',
            'description': 'Au lendemain de la victoire du "oui" au référendum sur l\'aéroport de Notre-Dame-des-Landes, l\'ancienne ministre écologiste est l\'invitée de Patrick Cohen. Plus d\'info : https://www.franceinter.fr/emissions/le-7-9/le-7-9-27-juin-2016',
            'timestamp': 1467011958,
            'upload_date': '20160627',
            'uploader': 'France Inter',
            'uploader_id': 'x2q2ez',
        },
        'add_ie': ['Dailymotion'],
    }, {
        'url': 'http://france3-regions.francetvinfo.fr/limousin/emissions/jt-1213-limousin',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        dailymotion_urls = DailymotionIE._extract_urls(webpage)
        if dailymotion_urls:
            return self.playlist_result([
                self.url_result(dailymotion_url, DailymotionIE.ie_key())
                for dailymotion_url in dailymotion_urls])

        video_id = self._search_regex(
            (r'player\.load[^;]+src:\s*["\']([^"\']+)',
             r'id-video=([^@]+@[^"]+)',
             r'<a[^>]+href="(?:https?:)?//videos\.francetv\.fr/video/([^@]+@[^"]+)"'),
            webpage, 'video id')

        return self._make_url_result(video_id)

class FranceTVInfoSportIE(FranceTVBaseInfoExtractor):
    IE_NAME = 'sport.francetvinfo.fr'
    _VALID_URL = r'https?://sport\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://sport.francetvinfo.fr/les-jeux-olympiques/retour-sur-les-meilleurs-moments-de-pyeongchang-2018',
        'info_dict': {
            'id': '6e49080e-3f45-11e8-b459-000d3a2439ea',
            'ext': 'mp4',
            'title': 'Retour sur les meilleurs moments de Pyeongchang 2018',
            'timestamp': 1523639962,
            'upload_date': '20180413',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [FranceTVIE.ie_key()],
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        video_id = self._search_regex(r'data-video="([^"]+)"', webpage, 'video_id')
        return self._make_url_result(video_id, 'Sport-web')

class GenerationWhatIE(InfoExtractor):
    IE_NAME = 'france2.fr:generation-what'
    _VALID_URL = r'https?://generation-what\.francetv\.fr/[^/]+/video/(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'http://generation-what.francetv.fr/portrait/video/present-arms',
        'info_dict': {
            'id': 'wtvKYUG45iw',
            'ext': 'mp4',
            'title': 'Generation What - Garde à vous - FRA',
            'uploader': 'Generation What',
            'uploader_id': 'UCHH9p1eetWCgt4kXBYCb3_w',
            'upload_date': '20160411',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': ['Youtube'],
    }, {
        'url': 'http://generation-what.francetv.fr/europe/video/present-arms',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        youtube_id = self._search_regex(
            r"window\.videoURL\s*=\s*'([0-9A-Za-z_-]{11})';",
            webpage, 'youtube id')

        return self.url_result(youtube_id, ie='Youtube', video_id=youtube_id)

class CultureboxIE(FranceTVBaseInfoExtractor):
    _VALID_URL = r'https?://(?:m\.)?culturebox\.francetvinfo\.fr/(?:[^/]+/)*(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'https://culturebox.francetvinfo.fr/opera-classique/musique-classique/c-est-baroque/concerts/cantates-bwv-4-106-et-131-de-bach-par-raphael-pichon-57-268689',
        'info_dict': {
            'id': 'EV_134885',
            'ext': 'mp4',
            'title': 'Cantates BWV 4, 106 et 131 de Bach par Raphaël Pichon 5/7',
            'description': 'md5:19c44af004b88219f4daa50fa9a351d4',
            'upload_date': '20180206',
            'timestamp': 1517945220,
            'duration': 5981,
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [FranceTVIE.ie_key()],
    }]

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

        webpage = self._download_webpage(url, display_id)

        if ">Ce live n'est plus disponible en replay<" in webpage:
            raise ExtractorError(
                'Video %s is not available' % display_id, expected=True)

        video_id, catalogue = self._search_regex(
            r'["\'>]https?://videos\.francetv\.fr/video/([^@]+@.+?)["\'<]',
            webpage, 'video id').split('@')

        return self._make_url_result(video_id, catalogue)

class FranceTVJeunesseIE(FranceTVBaseInfoExtractor):
    _VALID_URL = r'(?P<url>https?://(?:www\.)?(?:zouzous|ludo)\.fr/heros/(?P<id>[^/?#&]+))'

    _TESTS = [{
        'url': 'https://www.zouzous.fr/heros/simon',
        'info_dict': {
            'id': 'simon',
        },
        'playlist_count': 9,
    }, {
        'url': 'https://www.ludo.fr/heros/ninjago',
        'info_dict': {
            'id': 'ninjago',
        },
        'playlist_count': 10,
    }, {
        'url': 'https://www.zouzous.fr/heros/simon?abc',
        'only_matching': True,
    }]

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

        playlist = self._download_json(
            '%s/%s' % (mobj.group('url'), 'playlist'), playlist_id)

        if not playlist.get('count'):
            raise ExtractorError(
                '%s is not available' % playlist_id, expected=True)

        entries = []
        for item in playlist['items']:
            identity = item.get('identity')
            if identity and isinstance(identity, compat_str):
                entries.append(self._make_url_result(identity))

        return self.playlist_result(entries, playlist_id)


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    float_or_none,
    get_element_by_class,
    get_element_by_id,
    unified_strdate,
)

class FreesoundIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?freesound\.org/people/[^/]+/sounds/(?P<id>[^/]+)'
    _TEST = {
        'url': 'http://www.freesound.org/people/miklovan/sounds/194503/',
        'md5': '12280ceb42c81f19a515c745eae07650',
        'info_dict': {
            'id': '194503',
            'ext': 'mp3',
            'title': 'gulls in the city.wav',
            'description': 'the sounds of seagulls in the city',
            'duration': 130.233,
            'uploader': 'miklovan',
            'upload_date': '20130715',
            'tags': list,
        }
    }

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

        webpage = self._download_webpage(url, audio_id)

        audio_url = self._og_search_property('audio', webpage, 'song url')
        title = self._og_search_property('audio:title', webpage, 'song title')

        description = self._html_search_regex(
            r'(?s)id=["\']sound_description["\'][^>]*>(.+?)
',
            webpage, 'description', fatal=False)

        duration = float_or_none(
            get_element_by_class('duration', webpage), scale=1000)

        upload_date = unified_strdate(get_element_by_id('sound_date', webpage))
        uploader = self._og_search_property(
            'audio:artist', webpage, 'uploader', fatal=False)

        channels = self._html_search_regex(
            r'Channels

(.+?)
', webpage,
            'channels info', fatal=False)

        tags_str = get_element_by_class('tags', webpage)
        tags = re.findall(r']+>([^<]+)', tags_str) if tags_str else None

        audio_urls = [audio_url]

        LQ_FORMAT = '-lq.mp3'
        if LQ_FORMAT in audio_url:
            audio_urls.append(audio_url.replace(LQ_FORMAT, '-hq.mp3'))

        formats = [{
            'url': format_url,
            'format_note': channels,
            'quality': quality,
        } for quality, format_url in enumerate(audio_urls)]
        self._sort_formats(formats)

        return {
            'id': audio_id,
            'title': title,
            'description': description,
            'duration': duration,
            'uploader': uploader,
            'upload_date': upload_date,
            'tags': tags,
            'formats': formats,
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from .youtube import YoutubeIE

class FreespeechIE(InfoExtractor):
    IE_NAME = 'freespeech.org'
    _VALID_URL = r'https?://(?:www\.)?freespeech\.org/stories/(?P<id>.+)'
    _TEST = {
        'add_ie': ['Youtube'],
        'url': 'http://www.freespeech.org/stories/fcc-announces-net-neutrality-rollback-whats-stake/',
        'info_dict': {
            'id': 'waRk6IPqyWM',
            'ext': 'mp4',
            'title': 'What\'s At Stake - Net Neutrality Special',
            'description': 'Presented by MNN and FSTV',
            'upload_date': '20170728',
            'uploader_id': 'freespeechtv',
            'uploader': 'freespeechtv',
        },
    }

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        youtube_url = self._search_regex(
            r'data-video-url="([^"]+)"',
            webpage, 'youtube url')

        return self.url_result(youtube_url, YoutubeIE.ie_key())


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

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

class FreshLiveIE(InfoExtractor):
    _VALID_URL = r'https?://freshlive\.tv/[^/]+/(?P<id>\d+)'
    _TEST = {
        'url': 'https://freshlive.tv/satotv/74712',
        'md5': '9f0cf5516979c4454ce982df3d97f352',
        'info_dict': {
            'id': '74712',
            'ext': 'mp4',
            'title': 'テスト',
            'description': 'テスト',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1511,
            'timestamp': 1483619655,
            'upload_date': '20170105',
            'uploader': 'サトTV',
            'uploader_id': 'satotv',
            'view_count': int,
            'comment_count': int,
            'is_live': False,
        }
    }

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

        webpage = self._download_webpage(url, video_id)

        options = self._parse_json(
            self._search_regex(
                r'window\.__CONTEXT__\s*=\s*({.+?});\s*</script>',
                webpage, 'initial context'),
            video_id)

        info = options['context']['dispatcher']['stores']['ProgramStore']['programs'][video_id]

        title = info['title']

        if info.get('status') == 'upcoming':
            raise ExtractorError('Stream %s is upcoming' % video_id, expected=True)

        stream_url = info.get('liveStreamUrl') or info['archiveStreamUrl']

        is_live = info.get('liveStreamUrl') is not None

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

        if is_live:
            title = self._live_title(title)

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'description': info.get('description'),
            'thumbnail': info.get('thumbnailUrl'),
            'duration': int_or_none(info.get('airTime')),
            'timestamp': unified_timestamp(info.get('createdAt')),
            'uploader': try_get(
                info, lambda x: x['channel']['title'], compat_str),
            'uploader_id': try_get(
                info, lambda x: x['channel']['code'], compat_str),
            'uploader_url': try_get(
                info, lambda x: x['channel']['permalink'], compat_str),
            'view_count': int_or_none(info.get('viewCount')),
            'comment_count': int_or_none(info.get('commentCount')),
            'tags': info.get('tags', []),
            'is_live': is_live,
        }


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

import re

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    parse_duration,
    url_or_none,
    urlencode_postdata,
)

class FrontendMastersBaseIE(InfoExtractor):
    _API_BASE = 'https://api.frontendmasters.com/v1/kabuki'
    _LOGIN_URL = 'https://frontendmasters.com/login/'

    _NETRC_MACHINE = 'frontendmasters'

    _QUALITIES = {
        'low': {'width': 480, 'height': 360},
        'mid': {'width': 1280, 'height': 720},
        'high': {'width': 1920, 'height': 1080}
    }

    def _real_initialize(self):
        self._login()

    def _login(self):
        (username, password) = self._get_login_info()
        if username is None:
            return

        login_page = self._download_webpage(
            self._LOGIN_URL, None, 'Downloading login page')

        login_form = self._hidden_inputs(login_page)

        login_form.update({
            'username': username,
            'password': password
        })

        post_url = self._search_regex(
            r'<form[^>]+action=(["\'])(?P<url>.+?)\1', login_page,
            'post_url', default=self._LOGIN_URL, group='url')

        if not post_url.startswith('http'):
            post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)

        response = self._download_webpage(
            post_url, None, 'Logging in', data=urlencode_postdata(login_form),
            headers={'Content-Type': 'application/x-www-form-urlencoded'})

        # Successful login
        if any(p in response for p in (
                'wp-login.php?action=logout', '>Logout')):
            return

        error = self._html_search_regex(
            r'class=(["\'])(?:(?!\1).)*\bMessageAlert\b(?:(?!\1).)*\1[^>]*>(?P<error>[^<]+)<',
            response, 'error message', default=None, group='error')
        if error:
            raise ExtractorError('Unable to login: %s' % error, expected=True)
        raise ExtractorError('Unable to log in')

class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
    def _download_course(self, course_name, url):
        return self._download_json(
            '%s/courses/%s' % (self._API_BASE, course_name), course_name,
            'Downloading course JSON', headers={'Referer': url})

    @staticmethod
    def _extract_chapters(course):
        chapters = []
        lesson_elements = course.get('lessonElements')
        if isinstance(lesson_elements, list):
            chapters = [url_or_none(e) for e in lesson_elements if url_or_none(e)]
        return chapters

    @staticmethod
    def _extract_lesson(chapters, lesson_id, lesson):
        title = lesson.get('title') or lesson_id
        display_id = lesson.get('slug')
        description = lesson.get('description')
        thumbnail = lesson.get('thumbnail')

        chapter_number = None
        index = lesson.get('index')
        element_index = lesson.get('elementIndex')
        if (isinstance(index, int) and isinstance(element_index, int)
                and index < element_index):
            chapter_number = element_index - index
        chapter = (chapters[chapter_number - 1]
                   if chapter_number - 1 < len(chapters) else None)

        duration = None
        timestamp = lesson.get('timestamp')
        if isinstance(timestamp, compat_str):
            mobj = re.search(
                r'(?P<start>\d{1,2}:\d{1,2}:\d{1,2})\s*-(?P<end>\s*\d{1,2}:\d{1,2}:\d{1,2})',
                timestamp)
            if mobj:
                duration = parse_duration(mobj.group('end')) - parse_duration(
                    mobj.group('start'))

        return {
            '_type': 'url_transparent',
            'url': 'frontendmasters:%s' % lesson_id,
            'ie_key': FrontendMastersIE.ie_key(),
            'id': lesson_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'duration': duration,
            'chapter': chapter,
            'chapter_number': chapter_number,
        }

class FrontendMastersIE(FrontendMastersBaseIE):
    _VALID_URL = r'(?:frontendmasters:|https?://api\.frontendmasters\.com/v\d+/kabuki/video/)(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://api.frontendmasters.com/v1/kabuki/video/a2qogef6ba',
        'md5': '7f161159710d6b7016a4f4af6fcb05e2',
        'info_dict': {
            'id': 'a2qogef6ba',
            'ext': 'mp4',
            'title': 'a2qogef6ba',
        },
        'skip': 'Requires FrontendMasters account credentials',
    }, {
        'url': 'frontendmasters:a2qogef6ba',
        'only_matching': True,
    }]

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

        source_url = '%s/video/%s/source' % (self._API_BASE, lesson_id)

        formats = []
        for ext in ('webm', 'mp4'):
            for quality in ('low', 'mid', 'high'):
                resolution = self._QUALITIES[quality].copy()
                format_id = '%s-%s' % (ext, quality)
                format_url = self._download_json(
                    source_url, lesson_id,
                    'Downloading %s source JSON' % format_id, query={
                        'f': ext,
                        'r': resolution['height'],
                    }, headers={
                        'Referer': url,
                    }, fatal=False)['url']

                if not format_url:
                    continue

                f = resolution.copy()
                f.update({
                    'url': format_url,
                    'ext': ext,
                    'format_id': format_id,
                })
                formats.append(f)
        self._sort_formats(formats)

        subtitles = {
            'en': [{
                'url': '%s/transcripts/%s.vtt' % (self._API_BASE, lesson_id),
            }]
        }

        return {
            'id': lesson_id,
            'title': lesson_id,
            'formats': formats,
            'subtitles': subtitles
        }

class FrontendMastersLessonIE(FrontendMastersPageBaseIE):
    _VALID_URL = r'https?://(?:www\.)?frontendmasters\.com/courses/(?P<course_name>[^/]+)/(?P<lesson_name>[^/]+)'
    _TEST = {
        'url': 'https://frontendmasters.com/courses/web-development/tools',
        'info_dict': {
            'id': 'a2qogef6ba',
            'display_id': 'tools',
            'ext': 'mp4',
            'title': 'Tools',
            'description': 'md5:82c1ea6472e88ed5acd1829fe992e4f7',
            'thumbnail': r're:^https?://.*\.jpg$',
            'chapter': 'Introduction',
            'chapter_number': 1,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Requires FrontendMasters account credentials',
    }

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

        course = self._download_course(course_name, url)

        lesson_id, lesson = next(
            (video_id, data)
            for video_id, data in course['lessonData'].items()
            if data.get('slug') == lesson_name)

        chapters = self._extract_chapters(course)
        return self._extract_lesson(chapters, lesson_id, lesson)

class FrontendMastersCourseIE(FrontendMastersPageBaseIE):
    _VALID_URL = r'https?://(?:www\.)?frontendmasters\.com/courses/(?P<id>[^/]+)'
    _TEST = {
        'url': 'https://frontendmasters.com/courses/web-development/',
        'info_dict': {
            'id': 'web-development',
            'title': 'Introduction to Web Development',
            'description': 'md5:9317e6e842098bf725d62360e52d49a6',
        },
        'playlist_count': 81,
        'skip': 'Requires FrontendMasters account credentials',
    }

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

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

        course = self._download_course(course_name, url)

        chapters = self._extract_chapters(course)

        lessons = sorted(
            course['lessonData'].values(), key=lambda data: data['index'])

        entries = []
        for lesson in lessons:
            lesson_name = lesson.get('slug')
            if not lesson_name:
                continue
            lesson_id = lesson.get('hash') or lesson.get('statsId')
            entries.append(self._extract_lesson(chapters, lesson_id, lesson))

        title = course.get('title')
        description = course.get('description')

        return self.playlist_result(entries, course_name, title, description)


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

import random
import string

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    determine_ext,
    int_or_none,
    js_to_json,
    ExtractorError,
    urlencode_postdata
)

class FunimationIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?funimation(?:\.com|now\.uk)/shows/[^/]+/(?P<id>[^/?#&]+)'

    _NETRC_MACHINE = 'funimation'
    _TOKEN = None

    _TESTS = [{
        'url': 'https://www.funimation.com/shows/hacksign/role-play/',
        'info_dict': {
            'id': '91144',
            'display_id': 'role-play',
            'ext': 'mp4',
            'title': '.hack//SIGN - Role Play',
            'description': 'md5:b602bdc15eef4c9bbb201bb6e6a4a2dd',
            'thumbnail': r're:https?://.*\.jpg',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'https://www.funimation.com/shows/attack-on-titan-junior-high/broadcast-dub-preview/',
        'info_dict': {
            'id': '210051',
            'display_id': 'broadcast-dub-preview',
            'ext': 'mp4',
            'title': 'Attack on Titan: Junior High - Broadcast Dub Preview',
            'thumbnail': r're:https?://.*\.(?:jpg|png)',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'https://www.funimationnow.uk/shows/puzzle-dragons-x/drop-impact/simulcast/',
        'only_matching': True,
    }]

    def _login(self):
        username, password = self._get_login_info()
        if username is None:
            return
        try:
            data = self._download_json(
                'https://prod-api-funimationnow.dadcdigital.com/api/auth/login/',
                None, 'Logging in', data=urlencode_postdata({
                    'username': username,
                    'password': password,
                }))
            self._TOKEN = data['token']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                error = self._parse_json(e.cause.read().decode(), None)['error']
                raise ExtractorError(error, expected=True)
            raise

    def _real_initialize(self):
        self._login()

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

        def _search_kane(name):
            return self._search_regex(
                r"KANE_customdimensions\.%s\s*=\s*'([^']+)';" % name,
                webpage, name, default=None)

        title_data = self._parse_json(self._search_regex(
            r'TITLE_DATA\s*=\s*({[^}]+})',
            webpage, 'title data', default=''),
            display_id, js_to_json, fatal=False) or {}

        video_id = title_data.get('id') or self._search_regex([
            r"KANE_customdimensions.videoID\s*=\s*'(\d+)';",
            r'<iframe[^>]+src="/player/(\d+)',
        ], webpage, 'video_id', default=None)
        if not video_id:
            player_url = self._html_search_meta([
                'al:web:url',
                'og:video:url',
                'og:video:secure_url',
            ], webpage, fatal=True)
            video_id = self._search_regex(r'/player/(\d+)', player_url, 'video id')

        title = episode = title_data.get('title') or _search_kane('videoTitle') or self._og_search_title(webpage)
        series = _search_kane('showName')
        if series:
            title = '%s - %s' % (series, title)
        description = self._html_search_meta(['description', 'og:description'], webpage, fatal=True)

        try:
            headers = {}
            if self._TOKEN:
                headers['Authorization'] = 'Token %s' % self._TOKEN
            sources = self._download_json(
                'https://www.funimation.com/api/showexperience/%s/' % video_id,
                video_id, headers=headers, query={
                    'pinst_id': ''.join([random.choice(string.digits + string.ascii_letters) for _ in range(8)]),
                })['items']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                error = self._parse_json(e.cause.read(), video_id)['errors'][0]
                raise ExtractorError('%s said: %s' % (
                    self.IE_NAME, error.get('detail') or error.get('title')), expected=True)
            raise

        formats = []
        for source in sources:
            source_url = source.get('src')
            if not source_url:
                continue
            source_type = source.get('videoType') or determine_ext(source_url)
            if source_type == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    source_url, video_id, 'mp4',
                    m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'format_id': source_type,
                    'url': source_url,
                })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': self._og_search_thumbnail(webpage),
            'series': series,
            'season_number': int_or_none(title_data.get('seasonNum') or _search_kane('season')),
            'episode_number': int_or_none(title_data.get('episodeNum')),
            'episode': episode,
            'season_id': title_data.get('seriesId'),
            'formats': formats,
        }


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

import re

from .common import InfoExtractor
from .nexx import NexxIE
from ..utils import (
    int_or_none,
    str_or_none,
)

class FunkIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?funk\.net/(?:channel|playlist)/[^/]+/(?P<display_id>[0-9a-z-]+)-(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.funk.net/channel/ba-793/die-lustigsten-instrumente-aus-dem-internet-teil-2-1155821',
        'md5': '8dd9d9ab59b4aa4173b3197f2ea48e81',
        'info_dict': {
            'id': '1155821',
            'ext': 'mp4',
            'title': 'Die LUSTIGSTEN INSTRUMENTE aus dem Internet - Teil 2',
            'description': 'md5:a691d0413ef4835588c5b03ded670c1f',
            'timestamp': 1514507395,
            'upload_date': '20171229',
        },

    }, {
        'url': 'https://www.funk.net/playlist/neuesteVideos/kameras-auf-dem-fusion-festival-1618699',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id, nexx_id = re.match(self._VALID_URL, url).groups()
        video = self._download_json(
            'https://www.funk.net/api/v4.0/videos/' + nexx_id, nexx_id)
        return {
            '_type': 'url_transparent',
            'url': 'nexx:741:' + nexx_id,
            'ie_key': NexxIE.ie_key(),
            'id': nexx_id,
            'title': video.get('title'),
            'description': video.get('description'),
            'duration': int_or_none(video.get('duration')),
            'channel_id': str_or_none(video.get('channelId')),
            'display_id': display_id,
            'tags': video.get('tags'),
            'thumbnail': video.get('imageUrlLandscape'),
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    int_or_none,
    mimetype2ext,
    parse_iso8601,
)

class FusionIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?fusion\.(?:net|tv)/(?:video/|show/.+?\bvideo=)(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://fusion.tv/video/201781/u-s-and-panamanian-forces-work-together-to-stop-a-vessel-smuggling-drugs/',
        'info_dict': {
            'id': '3145868',
            'ext': 'mp4',
            'title': 'U.S. and Panamanian forces work together to stop a vessel smuggling drugs',
            'description': 'md5:0cc84a9943c064c0f46b128b41b1b0d7',
            'duration': 140.0,
            'timestamp': 1442589635,
            'uploader': 'UNIVISON',
            'upload_date': '20150918',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': ['Anvato'],
    }, {
        'url': 'http://fusion.tv/video/201781',
        'only_matching': True,
    }, {
        'url': 'https://fusion.tv/show/food-exposed-with-nelufar-hedayat/?ancla=full-episodes&video=588644',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video = self._download_json(
            'https://platform.fusion.net/wp-json/fusiondotnet/v1/video/' + video_id, video_id)

        info = {
            'id': video_id,
            'title': video['title'],
            'description': video.get('excerpt'),
            'timestamp': parse_iso8601(video.get('published')),
            'series': video.get('show'),
        }

        formats = []
        src = video.get('src') or {}
        for f_id, f in src.items():
            for q_id, q in f.items():
                q_url = q.get('url')
                if not q_url:
                    continue
                ext = determine_ext(q_url, mimetype2ext(q.get('type')))
                if ext == 'smil':
                    formats.extend(self._extract_smil_formats(q_url, video_id, fatal=False))
                elif f_id == 'm3u8-variant' or (ext == 'm3u8' and q_id == 'Variant'):
                    formats.extend(self._extract_m3u8_formats(
                        q_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
                else:
                    formats.append({
                        'format_id': '-'.join([f_id, q_id]),
                        'url': q_url,
                        'width': int_or_none(q.get('width')),
                        'height': int_or_none(q.get('height')),
                        'tbr': int_or_none(self._search_regex(r'_(\d+)\.m(?:p4|3u8)', q_url, 'bitrate')),
                        'ext': 'mp4' if ext == 'm3u8' else ext,
                        'protocol': 'm3u8_native' if ext == 'm3u8' else 'https',
                    })
        if formats:
            self._sort_formats(formats)
            info['formats'] = formats
        else:
            info.update({
                '_type': 'url',
                'url': 'anvato:uni:' + video['video_ids']['anvato'],
                'ie_key': 'Anvato',
            })

        return info