code:
from __future__ import unicode_literals

from .common import InfoExtractor

class LemondeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:.+?\.)?lemonde\.fr/(?:[^/]+/)*(?P<id>[^/]+)\.html'
    _TESTS = [{
        'url': 'http://www.lemonde.fr/police-justice/video/2016/01/19/comprendre-l-affaire-bygmalion-en-cinq-minutes_4849702_1653578.html',
        'md5': 'da120c8722d8632eec6ced937536cc98',
        'info_dict': {
            'id': 'lqm3kl',
            'ext': 'mp4',
            'title': "Comprendre l'affaire Bygmalion en 5 minutes",
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 309,
            'upload_date': '20160119',
            'timestamp': 1453194778,
            'uploader_id': '3pmkp',
        },
    }, {
        # standard iframe embed
        'url': 'http://www.lemonde.fr/les-decodeurs/article/2016/10/18/tout-comprendre-du-ceta-le-petit-cousin-du-traite-transatlantique_5015920_4355770.html',
        'info_dict': {
            'id': 'uzsxms',
            'ext': 'mp4',
            'title': "CETA : quelles suites pour l'accord commercial entre l'Europe et le Canada ?",
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 325,
            'upload_date': '20161021',
            'timestamp': 1477044540,
            'uploader_id': '3pmkp',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://redaction.actu.lemonde.fr/societe/video/2016/01/18/calais-debut-des-travaux-de-defrichement-dans-la-jungle_4849233_3224.html',
        'only_matching': True,
    }, {
        # YouTube embeds
        'url': 'http://www.lemonde.fr/pixels/article/2016/12/09/pourquoi-pewdiepie-superstar-de-youtube-a-menace-de-fermer-sa-chaine_5046649_4408996.html',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        digiteka_url = self._proto_relative_url(self._search_regex(
            r'url\s*:\s*(["\'])(?P<url>(?:https?://)?//(?:www\.)?(?:digiteka\.net|ultimedia\.com)/deliver/.+?)\1',
            webpage, 'digiteka url', group='url', default=None))

        if digiteka_url:
            return self.url_result(digiteka_url, 'Digiteka')

        return self.url_result(url, 'Generic')


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

from .common import InfoExtractor

class LentaIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?lenta\.ru/[^/]+/\d+/\d+/\d+/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://lenta.ru/news/2018/03/22/savshenko_go/',
        'info_dict': {
            'id': '964400',
            'ext': 'mp4',
            'title': 'Надежду Савченко задержали',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 61,
            'view_count': int,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # EaglePlatform iframe embed
        'url': 'http://lenta.ru/news/2015/03/06/navalny/',
        'info_dict': {
            'id': '227304',
            'ext': 'mp4',
            'title': 'Навальный вышел на свободу',
            'description': 'md5:d97861ac9ae77377f3f20eaf9d04b4f5',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 87,
            'view_count': int,
            'age_limit': 0,
        },
        'params': {
            'skip_download': True,
        },
    }]

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

        webpage = self._download_webpage(url, display_id)

        video_id = self._search_regex(
            r'vid\s*:\s*["\']?(\d+)', webpage, 'eagleplatform id',
            default=None)
        if video_id:
            return self.url_result(
                'eagleplatform:lentaru.media.eagleplatform.com:%s' % video_id,
                ie='EaglePlatform', video_id=video_id)

        return self.url_result(url, ie='Generic')

repo_name: rrosajp/youtube


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

import re

from .common import InfoExtractor

from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    parse_filesize,
)

class LibraryOfCongressIE(InfoExtractor):
    IE_NAME = 'loc'
    IE_DESC = 'Library of Congress'
    _VALID_URL = r'https?://(?:www\.)?loc\.gov/(?:item/|today/cyberlc/feature_wdesc\.php\?.*\brec=)(?P<id>[0-9a-z_.]+)'
    _TESTS = [{
        # embedded via <div class="media-player"
        'url': 'http://loc.gov/item/90716351/',
        'md5': '6ec0ae8f07f86731b1b2ff70f046210a',
        'info_dict': {
            'id': '90716351',
            'ext': 'mp4',
            'title': "Pa's trip to Mars",
            'duration': 0,
            'view_count': int,
        },
    }, {
        # webcast embedded via mediaObjectId
        'url': 'https://www.loc.gov/today/cyberlc/feature_wdesc.php?rec=5578',
        'info_dict': {
            'id': '5578',
            'ext': 'mp4',
            'title': 'Help! Preservation Training Needs Here, There & Everywhere',
            'duration': 3765,
            'view_count': int,
            'subtitles': 'mincount:1',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # with direct download links
        'url': 'https://www.loc.gov/item/78710669/',
        'info_dict': {
            'id': '78710669',
            'ext': 'mp4',
            'title': 'La vie et la passion de Jesus-Christ',
            'duration': 0,
            'view_count': int,
            'formats': 'mincount:4',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.loc.gov/item/ihas.200197114/',
        'only_matching': True,
    }, {
        'url': 'https://www.loc.gov/item/afc1981005_afs20503/',
        'only_matching': True,
    }]

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

        media_id = self._search_regex(
            (r'id=(["\'])media-player-(?P<id>.+?)\1',
             r'<video[^>]+id=(["\'])uuid-(?P<id>.+?)\1',
             r'<video[^>]+data-uuid=(["\'])(?P<id>.+?)\1',
             r'mediaObjectId\s*:\s*(["\'])(?P<id>.+?)\1',
             r'data-tab="share-media-(?P<id>[0-9A-F]{32})"'),
            webpage, 'media id', group='id')

        data = self._download_json(
            'https://media.loc.gov/services/v1/media?id=%s&context=json' % media_id,
            media_id)['mediaObject']

        derivative = data['derivatives'][0]
        media_url = derivative['derivativeUrl']

        title = derivative.get('shortName') or data.get('shortName') or self._og_search_title(
            webpage)

        # Following algorithm was extracted from setAVSource js function
        # found in webpage
        media_url = media_url.replace('rtmp', 'https')

        is_video = data.get('mediaType', 'v').lower() == 'v'
        ext = determine_ext(media_url)
        if ext not in ('mp4', 'mp3'):
            media_url += '.mp4' if is_video else '.mp3'

        formats = []
        if '/vod/mp4:' in media_url:
            formats.append({
                'url': media_url.replace('/vod/mp4:', '/hls-vod/media/') + '.m3u8',
                'format_id': 'hls',
                'ext': 'mp4',
                'protocol': 'm3u8_native',
                'quality': 1,
            })
        http_format = {
            'url': re.sub(r'(://[^/]+/)(?:[^/]+/)*(?:mp4|mp3):', r'\1', media_url),
            'format_id': 'http',
            'quality': 1,
        }
        if not is_video:
            http_format['vcodec'] = 'none'
        formats.append(http_format)

        download_urls = set()
        for m in re.finditer(
                r'<option[^>]+value=(["\'])(?P<url>.+?)\1[^>]+data-file-download=[^>]+>\s*(?P<id>.+?)(?:(?: |\s+)\((?P<size>.+?)\))?\s*<', webpage):
            format_id = m.group('id').lower()
            if format_id in ('gif', 'jpeg'):
                continue
            download_url = m.group('url')
            if download_url in download_urls:
                continue
            download_urls.add(download_url)
            formats.append({
                'url': download_url,
                'format_id': format_id,
                'filesize_approx': parse_filesize(m.group('size')),
            })

        self._sort_formats(formats)

        duration = float_or_none(data.get('duration'))
        view_count = int_or_none(data.get('viewCount'))

        subtitles = {}
        cc_url = data.get('ccUrl')
        if cc_url:
            subtitles.setdefault('en', []).append({
                'url': cc_url,
                'ext': 'ttml',
            })

        return {
            'id': video_id,
            'title': title,
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'duration': duration,
            'view_count': view_count,
            'formats': formats,
            'subtitles': subtitles,
        }


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

import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    get_element_by_class,
    parse_duration,
    strip_or_none,
    unified_strdate,
)

class LibsynIE(InfoExtractor):
    _VALID_URL = r'(?P<mainurl>https?://html5-player\.libsyn\.com/embed/episode/id/(?P<id>[0-9]+))'

    _TESTS = [{
        'url': 'http://html5-player.libsyn.com/embed/episode/id/6385796/',
        'md5': '2a55e75496c790cdeb058e7e6c087746',
        'info_dict': {
            'id': '6385796',
            'ext': 'mp3',
            'title': "Champion Minded - Developing a Growth Mindset",
            # description fetched using another request:
            # http://html5-player.libsyn.com/embed/getitemdetails?item_id=6385796
            # 'description': 'In this episode, Allistair talks about the importance of developing a growth mindset, not only in sports, but in life too.',
            'upload_date': '20180320',
            'thumbnail': 're:^https?://.*',
        },
    }, {
        'url': 'https://html5-player.libsyn.com/embed/episode/id/3727166/height/75/width/200/theme/standard/direction/no/autoplay/no/autonext/no/thumbnail/no/preload/no/no_addthis/no/',
        'md5': '6c5cb21acd622d754d3b1a92b582ce42',
        'info_dict': {
            'id': '3727166',
            'ext': 'mp3',
            'title': 'Clients From Hell Podcast - How a Sex Toy Company Kickstarted my Freelance Career',
            'upload_date': '20150818',
            'thumbnail': 're:^https?://.*',
        }
    }]

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

        data = self._parse_json(self._search_regex(
            r'var\s+playlistItem\s*=\s*({.+?});',
            webpage, 'JSON data block'), video_id)

        episode_title = data.get('item_title') or get_element_by_class('episode-title', webpage)
        if not episode_title:
            self._search_regex(
                [r'data-title="([^"]+)"', r'<title>(.+?)</title>'],
                webpage, 'episode title')
        episode_title = episode_title.strip()

        podcast_title = strip_or_none(clean_html(self._search_regex(
            r'
([^<]+)
', webpage, 'podcast title',
            default=None) or get_element_by_class('podcast-title', webpage)))

        title = '%s - %s' % (podcast_title, episode_title) if podcast_title else episode_title

        formats = []
        for k, format_id in (('media_url_libsyn', 'libsyn'), ('media_url', 'main'), ('download_link', 'download')):
            f_url = data.get(k)
            if not f_url:
                continue
            formats.append({
                'url': f_url,
                'format_id': format_id,
            })

        description = self._html_search_regex(
            r'
(.+?)
', webpage,
            'description', default=None)
        if description:
            # Strip non-breaking and normal spaces
            description = description.replace('\u00A0', ' ').strip()
        release_date = unified_strdate(self._search_regex(
            r'
Released: ([^<]+)<',
            webpage, 'release date', default=None) or data.get('release_date'))

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': data.get('thumbnail_url'),
            'upload_date': release_date,
            'duration': parse_duration(data.get('duration')),
            'formats': formats,
        }


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 (
    determine_ext,
    ExtractorError,
    int_or_none,
    parse_iso8601,
    remove_end,
)

class LifeNewsIE(InfoExtractor):
    IE_NAME = 'life'
    IE_DESC = 'Life.ru'
    _VALID_URL = r'https?://life\.ru/t/[^/]+/(?P<id>\d+)'

    _TESTS = [{
        # single video embedded via video/source
        'url': 'https://life.ru/t/новости/98736',
        'md5': '77c95eaefaca216e32a76a343ad89d23',
        'info_dict': {
            'id': '98736',
            'ext': 'mp4',
            'title': 'Мужчина нашел дома архив оборонного завода',
            'description': 'md5:3b06b1b39b5e2bea548e403d99b8bf26',
            'timestamp': 1344154740,
            'upload_date': '20120805',
            'view_count': int,
        }
    }, {
        # single video embedded via iframe
        'url': 'https://life.ru/t/новости/152125',
        'md5': '77d19a6f0886cd76bdbf44b4d971a273',
        'info_dict': {
            'id': '152125',
            'ext': 'mp4',
            'title': 'В Сети появилось видео захвата «Правым сектором» колхозных полей ',
            'description': 'Жители двух поселков Днепропетровской области не простили радикалам угрозу лишения плодородных земель и пошли в лобовую. ',
            'timestamp': 1427961840,
            'upload_date': '20150402',
            'view_count': int,
        }
    }, {
        # two videos embedded via iframe
        'url': 'https://life.ru/t/новости/153461',
        'info_dict': {
            'id': '153461',
            'title': 'В Москве спасли потерявшегося медвежонка, который спрятался на дереве',
            'description': 'Маленький хищник не смог найти дорогу домой и обрел временное убежище на тополе недалеко от жилого массива, пока его не нашла соседская собака.',
            'timestamp': 1430825520,
            'view_count': int,
        },
        'playlist': [{
            'md5': '9b6ef8bc0ffa25aebc8bdb40d89ab795',
            'info_dict': {
                'id': '153461-video1',
                'ext': 'mp4',
                'title': 'В Москве спасли потерявшегося медвежонка, который спрятался на дереве (Видео 1)',
                'description': 'Маленький хищник не смог найти дорогу домой и обрел временное убежище на тополе недалеко от жилого массива, пока его не нашла соседская собака.',
                'timestamp': 1430825520,
                'upload_date': '20150505',
            },
        }, {
            'md5': 'ebb3bf3b1ce40e878d0d628e93eb0322',
            'info_dict': {
                'id': '153461-video2',
                'ext': 'mp4',
                'title': 'В Москве спасли потерявшегося медвежонка, который спрятался на дереве (Видео 2)',
                'description': 'Маленький хищник не смог найти дорогу домой и обрел временное убежище на тополе недалеко от жилого массива, пока его не нашла соседская собака.',
                'timestamp': 1430825520,
                'upload_date': '20150505',
            },
        }],
    }, {
        'url': 'https://life.ru/t/новости/213035',
        'only_matching': True,
    }, {
        'url': 'https://life.ru/t/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8/153461',
        'only_matching': True,
    }, {
        'url': 'https://life.ru/t/новости/411489/manuel_vals_nazval_frantsiiu_tsieliu_nomier_odin_dlia_ighil',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        video_urls = re.findall(
            r'<video[^>]+><source[^>]+src=["\'](.+?)["\']', webpage)

        iframe_links = re.findall(
            r'<iframe[^>]+src=["\']((?:https?:)?//embed\.life\.ru/(?:embed|video)/.+?)["\']',
            webpage)

        if not video_urls and not iframe_links:
            raise ExtractorError('No media links available for %s' % video_id)

        title = remove_end(
            self._og_search_title(webpage),
            ' - Life.ru')

        description = self._og_search_description(webpage)

        view_count = self._html_search_regex(
            r'<div[^>]+class=(["\']).*?\bhits-count\b.*?\1[^>]*>\s*(?P<value>\d+)\s*</div>',
            webpage, 'view count', fatal=False, group='value')

        timestamp = parse_iso8601(self._search_regex(
            r'<time[^>]+datetime=(["\'])(?P<value>.+?)\1',
            webpage, 'upload date', fatal=False, group='value'))

        common_info = {
            'description': description,
            'view_count': int_or_none(view_count),
            'timestamp': timestamp,
        }

        def make_entry(video_id, video_url, index=None):
            cur_info = dict(common_info)
            cur_info.update({
                'id': video_id if not index else '%s-video%s' % (video_id, index),
                'url': video_url,
                'title': title if not index else '%s (Видео %s)' % (title, index),
            })
            return cur_info

        def make_video_entry(video_id, video_url, index=None):
            video_url = compat_urlparse.urljoin(url, video_url)
            return make_entry(video_id, video_url, index)

        def make_iframe_entry(video_id, video_url, index=None):
            video_url = self._proto_relative_url(video_url, 'http:')
            cur_info = make_entry(video_id, video_url, index)
            cur_info['_type'] = 'url_transparent'
            return cur_info

        if len(video_urls) == 1 and not iframe_links:
            return make_video_entry(video_id, video_urls[0])

        if len(iframe_links) == 1 and not video_urls:
            return make_iframe_entry(video_id, iframe_links[0])

        entries = []

        if video_urls:
            for num, video_url in enumerate(video_urls, 1):
                entries.append(make_video_entry(video_id, video_url, num))

        if iframe_links:
            for num, iframe_link in enumerate(iframe_links, len(video_urls) + 1):
                entries.append(make_iframe_entry(video_id, iframe_link, num))

        playlist = common_info.copy()
        playlist.update(self.playlist_result(entries, video_id, title, description))
        return playlist

class LifeEmbedIE(InfoExtractor):
    IE_NAME = 'life:embed'
    _VALID_URL = r'https?://embed\.life\.ru/(?:embed|video)/(?P<id>[\da-f]{32})'

    _TESTS = [{
        'url': 'http://embed.life.ru/embed/e50c2dec2867350528e2574c899b8291',
        'md5': 'b889715c9e49cb1981281d0e5458fbbe',
        'info_dict': {
            'id': 'e50c2dec2867350528e2574c899b8291',
            'ext': 'mp4',
            'title': 'e50c2dec2867350528e2574c899b8291',
            'thumbnail': r're:http://.*\.jpg',
        }
    }, {
        # with 1080p
        'url': 'https://embed.life.ru/video/e50c2dec2867350528e2574c899b8291',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        thumbnail = None
        formats = []

        def extract_m3u8(manifest_url):
            formats.extend(self._extract_m3u8_formats(
                manifest_url, video_id, 'mp4',
                entry_protocol='m3u8_native', m3u8_id='m3u8'))

        def extract_original(original_url):
            formats.append({
                'url': original_url,
                'format_id': determine_ext(original_url, None),
                'preference': 1,
            })

        playlist = self._parse_json(
            self._search_regex(
                r'options\s*=\s*({.+?});', webpage, 'options', default='{}'),
            video_id).get('playlist', {})
        if playlist:
            master = playlist.get('master')
            if isinstance(master, compat_str) and determine_ext(master) == 'm3u8':
                extract_m3u8(compat_urlparse.urljoin(url, master))
            original = playlist.get('original')
            if isinstance(original, compat_str):
                extract_original(original)
            thumbnail = playlist.get('image')

        # Old rendition fallback
        if not formats:
            for video_url in re.findall(r'"file"\s*:\s*"([^"]+)', webpage):
                video_url = compat_urlparse.urljoin(url, video_url)
                if determine_ext(video_url) == 'm3u8':
                    extract_m3u8(video_url)
                else:
                    extract_original(video_url)

        self._sort_formats(formats)

        thumbnail = thumbnail or self._search_regex(
            r'"image"\s*:\s*"([^"]+)', webpage, 'thumbnail', default=None)

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


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

import re

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    smuggle_url,
    try_get,
    unsmuggle_url,
    ExtractorError,
)

class LimelightBaseIE(InfoExtractor):
    _PLAYLIST_SERVICE_URL = 'http://production-ps.lvp.llnw.net/r/PlaylistService/%s/%s/%s'

    @classmethod
    def _extract_urls(cls, webpage, source_url):
        lm = {
            'Media': 'media',
            'Channel': 'channel',
            'ChannelList': 'channel_list',
        }

        def smuggle(url):
            return smuggle_url(url, {'source_url': source_url})

        entries = []
        for kind, video_id in re.findall(
                r'LimelightPlayer\.doLoad(Media|Channel|ChannelList)\(["\'](?P<id>[a-z0-9]{32})',
                webpage):
            entries.append(cls.url_result(
                smuggle('limelight:%s:%s' % (lm[kind], video_id)),
                'Limelight%s' % kind, video_id))
        for mobj in re.finditer(
                # As per [1] class attribute should be exactly equal to
                # LimelightEmbeddedPlayerFlash but numerous examples seen
                # that don't exactly match it (e.g. [2]).
                # 1. http://support.3playmedia.com/hc/en-us/articles/227732408-Limelight-Embedding-the-Captions-Plugin-with-the-Limelight-Player-on-Your-Webpage
                # 2. http://www.sedona.com/FacilitatorTraining2017
                r'''(?sx)
                    <object[^>]+class=(["\'])(?:(?!\1).)*\bLimelightEmbeddedPlayerFlash\b(?:(?!\1).)*\1[^>]*>.*?
                        <param[^>]+
                            name=(["\'])flashVars\2[^>]+
                            value=(["\'])(?:(?!\3).)*(?P<kind>media|channel(?:List)?)Id=(?P<id>[a-z0-9]{32})
                ''', webpage):
            kind, video_id = mobj.group('kind'), mobj.group('id')
            entries.append(cls.url_result(
                smuggle('limelight:%s:%s' % (kind, video_id)),
                'Limelight%s' % kind.capitalize(), video_id))
        # http://support.3playmedia.com/hc/en-us/articles/115009517327-Limelight-Embedding-the-Audio-Description-Plugin-with-the-Limelight-Player-on-Your-Web-Page)
        for video_id in re.findall(
                r'(?s)LimelightPlayerUtil\.embed\s*\(\s*{.*?\bmediaId["\']\s*:\s*["\'](?P<id>[a-z0-9]{32})',
                webpage):
            entries.append(cls.url_result(
                smuggle('limelight:media:%s' % video_id),
                LimelightMediaIE.ie_key(), video_id))
        return entries

    def _call_playlist_service(self, item_id, method, fatal=True, referer=None):
        headers = {}
        if referer:
            headers['Referer'] = referer
        try:
            return self._download_json(
                self._PLAYLIST_SERVICE_URL % (self._PLAYLIST_SERVICE_PATH, item_id, method),
                item_id, 'Downloading PlaylistService %s JSON' % method,
                fatal=fatal, headers=headers)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                error = self._parse_json(e.cause.read().decode(), item_id)['detail']['contentAccessPermission']
                if error == 'CountryDisabled':
                    self.raise_geo_restricted()
                raise ExtractorError(error, expected=True)
            raise

    def _extract(self, item_id, pc_method, mobile_method, referer=None):
        pc = self._call_playlist_service(item_id, pc_method, referer=referer)
        mobile = self._call_playlist_service(
            item_id, mobile_method, fatal=False, referer=referer)
        return pc, mobile

    def _extract_info(self, pc, mobile, i, referer):
        get_item = lambda x, y: try_get(x, lambda x: x[y][i], dict) or {}
        pc_item = get_item(pc, 'playlistItems')
        mobile_item = get_item(mobile, 'mediaList')
        video_id = pc_item.get('mediaId') or mobile_item['mediaId']
        title = pc_item.get('title') or mobile_item['title']

        formats = []
        urls = []
        for stream in pc_item.get('streams', []):
            stream_url = stream.get('url')
            if not stream_url or stream.get('drmProtected') or stream_url in urls:
                continue
            urls.append(stream_url)
            ext = determine_ext(stream_url)
            if ext == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    stream_url, video_id, f4m_id='hds', fatal=False))
            else:
                fmt = {
                    'url': stream_url,
                    'abr': float_or_none(stream.get('audioBitRate')),
                    'fps': float_or_none(stream.get('videoFrameRate')),
                    'ext': ext,
                }
                width = int_or_none(stream.get('videoWidthInPixels'))
                height = int_or_none(stream.get('videoHeightInPixels'))
                vbr = float_or_none(stream.get('videoBitRate'))
                if width or height or vbr:
                    fmt.update({
                        'width': width,
                        'height': height,
                        'vbr': vbr,
                    })
                else:
                    fmt['vcodec'] = 'none'
                rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', stream_url)
                if rtmp:
                    format_id = 'rtmp'
                    if stream.get('videoBitRate'):
                        format_id += '-%d' % int_or_none(stream['videoBitRate'])
                    http_format_id = format_id.replace('rtmp', 'http')

                    CDN_HOSTS = (
                        ('delvenetworks.com', 'cpl.delvenetworks.com'),
                        ('video.llnw.net', 's2.content.video.llnw.net'),
                    )
                    for cdn_host, http_host in CDN_HOSTS:
                        if cdn_host not in rtmp.group('host').lower():
                            continue
                        http_url = 'http://%s/%s' % (http_host, rtmp.group('playpath')[4:])
                        urls.append(http_url)
                        if self._is_valid_url(http_url, video_id, http_format_id):
                            http_fmt = fmt.copy()
                            http_fmt.update({
                                'url': http_url,
                                'format_id': http_format_id,
                            })
                            formats.append(http_fmt)
                            break

                    fmt.update({
                        'url': rtmp.group('url'),
                        'play_path': rtmp.group('playpath'),
                        'app': rtmp.group('app'),
                        'ext': 'flv',
                        'format_id': format_id,
                    })
                formats.append(fmt)

        for mobile_url in mobile_item.get('mobileUrls', []):
            media_url = mobile_url.get('mobileUrl')
            format_id = mobile_url.get('targetMediaPlatform')
            if not media_url or format_id in ('Widevine', 'SmoothStreaming') or media_url in urls:
                continue
            urls.append(media_url)
            ext = determine_ext(media_url)
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    media_url, video_id, 'mp4', 'm3u8_native',
                    m3u8_id=format_id, fatal=False))
            elif ext == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    stream_url, video_id, f4m_id=format_id, fatal=False))
            else:
                formats.append({
                    'url': media_url,
                    'format_id': format_id,
                    'preference': -1,
                    'ext': ext,
                })

        self._sort_formats(formats)

        subtitles = {}
        for flag in mobile_item.get('flags'):
            if flag == 'ClosedCaptions':
                closed_captions = self._call_playlist_service(
                    video_id, 'getClosedCaptionsDetailsByMediaId',
                    False, referer) or []
                for cc in closed_captions:
                    cc_url = cc.get('webvttFileUrl')
                    if not cc_url:
                        continue
                    lang = cc.get('languageCode') or self._search_regex(r'/[a-z]{2}\.vtt', cc_url, 'lang', default='en')
                    subtitles.setdefault(lang, []).append({
                        'url': cc_url,
                    })
                break

        get_meta = lambda x: pc_item.get(x) or mobile_item.get(x)

        return {
            'id': video_id,
            'title': title,
            'description': get_meta('description'),
            'formats': formats,
            'duration': float_or_none(get_meta('durationInMilliseconds'), 1000),
            'thumbnail': get_meta('previewImageUrl') or get_meta('thumbnailImageUrl'),
            'subtitles': subtitles,
        }

class LimelightMediaIE(LimelightBaseIE):
    IE_NAME = 'limelight'
    _VALID_URL = r'''(?x)
                        (?:
                            limelight:media:|
                            https?://
                                (?:
                                    link\.videoplatform\.limelight\.com/media/|
                                    assets\.delvenetworks\.com/player/loader\.swf
                                )
                                \?.*?\bmediaId=
                        )
                        (?P<id>[a-z0-9]{32})
                    '''
    _TESTS = [{
        'url': 'http://link.videoplatform.limelight.com/media/?mediaId=3ffd040b522b4485b6d84effc750cd86',
        'info_dict': {
            'id': '3ffd040b522b4485b6d84effc750cd86',
            'ext': 'mp4',
            'title': 'HaP and the HB Prince Trailer',
            'description': 'md5:8005b944181778e313d95c1237ddb640',
            'thumbnail': r're:^https?://.*\.jpeg$',
            'duration': 144.23,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        # video with subtitles
        'url': 'limelight:media:a3e00274d4564ec4a9b29b9466432335',
        'md5': '2fa3bad9ac321e23860ca23bc2c69e3d',
        'info_dict': {
            'id': 'a3e00274d4564ec4a9b29b9466432335',
            'ext': 'mp4',
            'title': '3Play Media Overview Video',
            'thumbnail': r're:^https?://.*\.jpeg$',
            'duration': 78.101,
            # TODO: extract all languages that were accessible via API
            # 'subtitles': 'mincount:9',
            'subtitles': 'mincount:1',
        },
    }, {
        'url': 'https://assets.delvenetworks.com/player/loader.swf?mediaId=8018a574f08d416e95ceaccae4ba0452',
        'only_matching': True,
    }]
    _PLAYLIST_SERVICE_PATH = 'media'

    def _real_extract(self, url):
        url, smuggled_data = unsmuggle_url(url, {})
        video_id = self._match_id(url)
        source_url = smuggled_data.get('source_url')
        self._initialize_geo_bypass({
            'countries': smuggled_data.get('geo_countries'),
        })

        pc, mobile = self._extract(
            video_id, 'getPlaylistByMediaId',
            'getMobilePlaylistByMediaId', source_url)

        return self._extract_info(pc, mobile, 0, source_url)

class LimelightChannelIE(LimelightBaseIE):
    IE_NAME = 'limelight:channel'
    _VALID_URL = r'''(?x)
                        (?:
                            limelight:channel:|
                            https?://
                                (?:
                                    link\.videoplatform\.limelight\.com/media/|
                                    assets\.delvenetworks\.com/player/loader\.swf
                                )
                                \?.*?\bchannelId=
                        )
                        (?P<id>[a-z0-9]{32})
                    '''
    _TESTS = [{
        'url': 'http://link.videoplatform.limelight.com/media/?channelId=ab6a524c379342f9b23642917020c082',
        'info_dict': {
            'id': 'ab6a524c379342f9b23642917020c082',
            'title': 'Javascript Sample Code',
            'description': 'Javascript Sample Code - http://www.delvenetworks.com/sample-code/playerCode-demo.html',
        },
        'playlist_mincount': 3,
    }, {
        'url': 'http://assets.delvenetworks.com/player/loader.swf?channelId=ab6a524c379342f9b23642917020c082',
        'only_matching': True,
    }]
    _PLAYLIST_SERVICE_PATH = 'channel'

    def _real_extract(self, url):
        url, smuggled_data = unsmuggle_url(url, {})
        channel_id = self._match_id(url)
        source_url = smuggled_data.get('source_url')

        pc, mobile = self._extract(
            channel_id, 'getPlaylistByChannelId',
            'getMobilePlaylistWithNItemsByChannelId?begin=0&count=-1',
            source_url)

        entries = [
            self._extract_info(pc, mobile, i, source_url)
            for i in range(len(pc['playlistItems']))]

        return self.playlist_result(
            entries, channel_id, pc.get('title'), mobile.get('description'))

class LimelightChannelListIE(LimelightBaseIE):
    IE_NAME = 'limelight:channel_list'
    _VALID_URL = r'''(?x)
                        (?:
                            limelight:channel_list:|
                            https?://
                                (?:
                                    link\.videoplatform\.limelight\.com/media/|
                                    assets\.delvenetworks\.com/player/loader\.swf
                                )
                                \?.*?\bchannelListId=
                        )
                        (?P<id>[a-z0-9]{32})
                    '''
    _TESTS = [{
        'url': 'http://link.videoplatform.limelight.com/media/?channelListId=301b117890c4465c8179ede21fd92e2b',
        'info_dict': {
            'id': '301b117890c4465c8179ede21fd92e2b',
            'title': 'Website - Hero Player',
        },
        'playlist_mincount': 2,
    }, {
        'url': 'https://assets.delvenetworks.com/player/loader.swf?channelListId=301b117890c4465c8179ede21fd92e2b',
        'only_matching': True,
    }]
    _PLAYLIST_SERVICE_PATH = 'channel_list'

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

        channel_list = self._call_playlist_service(
            channel_list_id, 'getMobileChannelListById')

        entries = [
            self.url_result('limelight:channel:%s' % channel['id'], 'LimelightChannel')
            for channel in channel_list['channelList']]

        return self.playlist_result(
            entries, channel_list_id, channel_list['title'])


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

import re

from .common import InfoExtractor
from ..utils import js_to_json

class LineTVIE(InfoExtractor):
    _VALID_URL = r'https?://tv\.line\.me/v/(?P<id>\d+)_[^/]+-(?P<segment>ep\d+-\d+)'

    _TESTS = [{
        'url': 'https://tv.line.me/v/793123_goodbye-mrblack-ep1-1/list/69246',
        'info_dict': {
            'id': '793123_ep1-1',
            'ext': 'mp4',
            'title': 'Goodbye Mr.Black | EP.1-1',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 998.509,
            'view_count': int,
        },
    }, {
        'url': 'https://tv.line.me/v/2587507_%E6%B4%BE%E9%81%A3%E5%A5%B3%E9%86%ABx-ep1-02/list/185245',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        series_id, segment = re.match(self._VALID_URL, url).groups()
        video_id = '%s_%s' % (series_id, segment)

        webpage = self._download_webpage(url, video_id)

        player_params = self._parse_json(self._search_regex(
            r'naver\.WebPlayer\(({[^}]+})\)', webpage, 'player parameters'),
            video_id, transform_source=js_to_json)

        video_info = self._download_json(
            'https://global-nvapis.line.me/linetv/rmcnmv/vod_play_videoInfo.json',
            video_id, query={
                'videoId': player_params['videoId'],
                'key': player_params['key'],
            })

        stream = video_info['streams'][0]
        extra_query = '?__gda__=' + stream['key']['value']
        formats = self._extract_m3u8_formats(
            stream['source'] + extra_query, video_id, ext='mp4',
            entry_protocol='m3u8_native', m3u8_id='hls')

        for a_format in formats:
            a_format['url'] += extra_query

        duration = None
        for video in video_info.get('videos', {}).get('list', []):
            encoding_option = video.get('encodingOption', {})
            abr = video['bitrate']['audio']
            vbr = video['bitrate']['video']
            tbr = abr + vbr
            formats.append({
                'url': video['source'],
                'format_id': 'http-%d' % int(tbr),
                'height': encoding_option.get('height'),
                'width': encoding_option.get('width'),
                'abr': abr,
                'vbr': vbr,
                'filesize': video.get('size'),
            })
            if video.get('duration') and duration is None:
                duration = video['duration']

        self._sort_formats(formats)

        if not formats[0].get('width'):
            formats[0]['vcodec'] = 'none'

        title = self._og_search_title(webpage)

        # like_count requires an additional API request https://tv.line.me/api/likeit/getCount

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'extra_param_to_segment_url': extra_query[1:],
            'duration': duration,
            'thumbnails': [{'url': thumbnail['source']}
                           for thumbnail in video_info.get('thumbnails', {}).get('list', [])],
            'view_count': video_info.get('meta', {}).get('count'),
        }


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

import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
    urlencode_postdata,
    urljoin,
)

class LinkedInLearningBaseIE(InfoExtractor):
    _NETRC_MACHINE = 'linkedin'
    _LOGIN_URL = 'https://www.linkedin.com/uas/login?trk=learning'

    def _call_api(self, course_slug, fields, video_slug=None, resolution=None):
        query = {
            'courseSlug': course_slug,
            'fields': fields,
            'q': 'slugs',
        }
        sub = ''
        if video_slug:
            query.update({
                'videoSlug': video_slug,
                'resolution': '_%s' % resolution,
            })
            sub = ' %dp' % resolution
        api_url = 'https://www.linkedin.com/learning-api/detailedCourses'
        return self._download_json(
            api_url, video_slug, 'Downloading%s JSON metadata' % sub, headers={
                'Csrf-Token': self._get_cookies(api_url)['JSESSIONID'].value,
            }, query=query)['elements'][0]

    def _get_urn_id(self, video_data):
        urn = video_data.get('urn')
        if urn:
            mobj = re.search(r'urn:li:lyndaCourse:\d+,(\d+)', urn)
            if mobj:
                return mobj.group(1)

    def _get_video_id(self, video_data, course_slug, video_slug):
        return self._get_urn_id(video_data) or '%s/%s' % (course_slug, video_slug)

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

        login_page = self._download_webpage(
            self._LOGIN_URL, None, 'Downloading login page')
        action_url = urljoin(self._LOGIN_URL, self._search_regex(
            r'<form[^>]+action=(["\'])(?P<url>.+?)\1', login_page, 'post url',
            default='https://www.linkedin.com/uas/login-submit', group='url'))
        data = self._hidden_inputs(login_page)
        data.update({
            'session_key': email,
            'session_password': password,
        })
        login_submit_page = self._download_webpage(
            action_url, None, 'Logging in',
            data=urlencode_postdata(data))
        error = self._search_regex(
            r'<span[^>]+class="error"[^>]*>\s*(.+?)\s*</span>',
            login_submit_page, 'error', default=None)
        if error:
            raise ExtractorError(error, expected=True)

class LinkedInLearningIE(LinkedInLearningBaseIE):
    IE_NAME = 'linkedin:learning'
    _VALID_URL = r'https?://(?:www\.)?linkedin\.com/learning/(?P<course_slug>[^/]+)/(?P<id>[^/?#]+)'
    _TEST = {
        'url': 'https://www.linkedin.com/learning/programming-foundations-fundamentals/welcome?autoplay=true',
        'md5': 'a1d74422ff0d5e66a792deb996693167',
        'info_dict': {
            'id': '90426',
            'ext': 'mp4',
            'title': 'Welcome',
            'timestamp': 1430396150.82,
            'upload_date': '20150430',
        },
    }

    def _real_extract(self, url):
        course_slug, video_slug = re.match(self._VALID_URL, url).groups()

        video_data = None
        formats = []
        for width, height in ((640, 360), (960, 540), (1280, 720)):
            video_data = self._call_api(
                course_slug, 'selectedVideo', video_slug, height)['selectedVideo']

            video_url_data = video_data.get('url') or {}
            progressive_url = video_url_data.get('progressiveUrl')
            if progressive_url:
                formats.append({
                    'format_id': 'progressive-%dp' % height,
                    'url': progressive_url,
                    'height': height,
                    'width': width,
                    'source_preference': 1,
                })

        title = video_data['title']

        audio_url = video_data.get('audio', {}).get('progressiveUrl')
        if audio_url:
            formats.append({
                'abr': 64,
                'ext': 'm4a',
                'format_id': 'audio',
                'url': audio_url,
                'vcodec': 'none',
            })

        streaming_url = video_url_data.get('streamingUrl')
        if streaming_url:
            formats.extend(self._extract_m3u8_formats(
                streaming_url, video_slug, 'mp4',
                'm3u8_native', m3u8_id='hls', fatal=False))

        self._sort_formats(formats, ('width', 'height', 'source_preference', 'tbr', 'abr'))

        return {
            'id': self._get_video_id(video_data, course_slug, video_slug),
            'title': title,
            'formats': formats,
            'thumbnail': video_data.get('defaultThumbnail'),
            'timestamp': float_or_none(video_data.get('publishedOn'), 1000),
            'duration': int_or_none(video_data.get('durationInSeconds')),
        }

class LinkedInLearningCourseIE(LinkedInLearningBaseIE):
    IE_NAME = 'linkedin:learning:course'
    _VALID_URL = r'https?://(?:www\.)?linkedin\.com/learning/(?P<id>[^/?#]+)'
    _TEST = {
        'url': 'https://www.linkedin.com/learning/programming-foundations-fundamentals',
        'info_dict': {
            'id': 'programming-foundations-fundamentals',
            'title': 'Programming Foundations: Fundamentals',
            'description': 'md5:76e580b017694eb89dc8e8923fff5c86',
        },
        'playlist_mincount': 61,
    }

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

    def _real_extract(self, url):
        course_slug = self._match_id(url)
        course_data = self._call_api(course_slug, 'chapters,description,title')

        entries = []
        for chapter_number, chapter in enumerate(course_data.get('chapters', []), 1):
            chapter_title = chapter.get('title')
            chapter_id = self._get_urn_id(chapter)
            for video in chapter.get('videos', []):
                video_slug = video.get('slug')
                if not video_slug:
                    continue
                entries.append({
                    '_type': 'url_transparent',
                    'id': self._get_video_id(video, course_slug, video_slug),
                    'title': video.get('title'),
                    'url': 'https://www.linkedin.com/learning/%s/%s' % (course_slug, video_slug),
                    'chapter': chapter_title,
                    'chapter_number': chapter_number,
                    'chapter_id': chapter_id,
                    'ie_key': LinkedInLearningIE.ie_key(),
                })

        return self.playlist_result(
            entries, course_slug,
            course_data.get('title'),
            course_data.get('description'))


code:
from __future__ import unicode_literals

import json
import random
import re

from .common import InfoExtractor
from ..compat import (
    compat_b64decode,
    compat_HTTPError,
)
from ..utils import (
    ExtractorError,
    orderedSet,
    unescapeHTML,
    urlencode_postdata,
    urljoin,
)

class LinuxAcademyIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.)?linuxacademy\.com/cp/
                        (?:
                            courses/lesson/course/(?P<chapter_id>\d+)/lesson/(?P<lesson_id>\d+)|
                            modules/view/id/(?P<course_id>\d+)
                        )
                    '''
    _TESTS = [{
        'url': 'https://linuxacademy.com/cp/courses/lesson/course/1498/lesson/2/module/154',
        'info_dict': {
            'id': '1498-2',
            'ext': 'mp4',
            'title': "Introduction to the Practitioner's Brief",
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Requires Linux Academy account credentials',
    }, {
        'url': 'https://linuxacademy.com/cp/courses/lesson/course/1498/lesson/2',
        'only_matching': True,
    }, {
        'url': 'https://linuxacademy.com/cp/modules/view/id/154',
        'info_dict': {
            'id': '154',
            'title': 'AWS Certified Cloud Practitioner',
            'description': 'md5:039db7e60e4aac9cf43630e0a75fa834',
        },
        'playlist_count': 41,
        'skip': 'Requires Linux Academy account credentials',
    }]

    _AUTHORIZE_URL = 'https://login.linuxacademy.com/authorize'
    _ORIGIN_URL = 'https://linuxacademy.com'
    _CLIENT_ID = 'KaWxNn1C2Gc7n83W9OFeXltd8Utb5vvx'
    _NETRC_MACHINE = 'linuxacademy'

    def _real_initialize(self):
        self._login()

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

        def random_string():
            return ''.join([
                random.choice('0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~')
                for _ in range(32)])

        webpage, urlh = self._download_webpage_handle(
            self._AUTHORIZE_URL, None, 'Downloading authorize page', query={
                'client_id': self._CLIENT_ID,
                'response_type': 'token id_token',
                'redirect_uri': self._ORIGIN_URL,
                'scope': 'openid email user_impersonation profile',
                'audience': self._ORIGIN_URL,
                'state': random_string(),
                'nonce': random_string(),
            })

        login_data = self._parse_json(
            self._search_regex(
                r'atob\(\s*(["\'])(?P<value>(?:(?!\1).)+)\1', webpage,
                'login info', group='value'), None,
            transform_source=lambda x: compat_b64decode(x).decode('utf-8')
        )['extraParams']

        login_data.update({
            'client_id': self._CLIENT_ID,
            'redirect_uri': self._ORIGIN_URL,
            'tenant': 'lacausers',
            'connection': 'Username-Password-Authentication',
            'username': username,
            'password': password,
            'sso': 'true',
        })

        login_state_url = urlh.geturl()

        try:
            login_page = self._download_webpage(
                'https://login.linuxacademy.com/usernamepassword/login', None,
                'Downloading login page', data=json.dumps(login_data).encode(),
                headers={
                    'Content-Type': 'application/json',
                    'Origin': 'https://login.linuxacademy.com',
                    'Referer': login_state_url,
                })
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                error = self._parse_json(e.cause.read(), None)
                message = error.get('description') or error['code']
                raise ExtractorError(
                    '%s said: %s' % (self.IE_NAME, message), expected=True)
            raise

        callback_page, urlh = self._download_webpage_handle(
            'https://login.linuxacademy.com/login/callback', None,
            'Downloading callback page',
            data=urlencode_postdata(self._hidden_inputs(login_page)),
            headers={
                'Content-Type': 'application/x-www-form-urlencoded',
                'Origin': 'https://login.linuxacademy.com',
                'Referer': login_state_url,
            })

        access_token = self._search_regex(
            r'access_token=([^=&]+)', urlh.geturl(),
            'access token')

        self._download_webpage(
            'https://linuxacademy.com/cp/login/tokenValidateLogin/token/%s'
            % access_token, None, 'Downloading token validation page')

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        chapter_id, lecture_id, course_id = mobj.group('chapter_id', 'lesson_id', 'course_id')
        item_id = course_id if course_id else '%s-%s' % (chapter_id, lecture_id)

        webpage = self._download_webpage(url, item_id)

        # course path
        if course_id:
            entries = [
                self.url_result(
                    urljoin(url, lesson_url), ie=LinuxAcademyIE.ie_key())
                for lesson_url in orderedSet(re.findall(
                    r'<a[^>]+\bhref=["\'](/cp/courses/lesson/course/\d+/lesson/\d+/module/\d+)',
                    webpage))]
            title = unescapeHTML(self._html_search_regex(
                (r'class=["\']course-title["\'][^>]*>(?P<value>[^<]+)',
                 r'var\s+title\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1'),
                webpage, 'title', default=None, group='value'))
            description = unescapeHTML(self._html_search_regex(
                r'var\s+description\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
                webpage, 'description', default=None, group='value'))
            return self.playlist_result(entries, course_id, title, description)

        # single video path
        info = self._extract_jwplayer_data(
            webpage, item_id, require_title=False, m3u8_id='hls',)
        title = self._search_regex(
            (r'>Lecture\s*:\s*(?P<value>[^<]+)',
             r'lessonName\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1'), webpage,
            'title', group='value')
        info.update({
            'id': item_id,
            'title': title,
        })
        return info


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

import json

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    smuggle_url,
    unsmuggle_url,
)

class LiTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?litv\.tv/(?:vod|promo)/[^/]+/(?:content\.do)?\?.*?\b(?:content_)?id=(?P<id>[^&]+)'

    _URL_TEMPLATE = 'https://www.litv.tv/vod/%s/content.do?id=%s'

    _TESTS = [{
        'url': 'https://www.litv.tv/vod/drama/content.do?brc_id=root&id=VOD00041610&isUHEnabled=true&autoPlay=1',
        'info_dict': {
            'id': 'VOD00041606',
            'title': '花千骨',
        },
        'playlist_count': 50,
    }, {
        'url': 'https://www.litv.tv/vod/drama/content.do?brc_id=root&id=VOD00041610&isUHEnabled=true&autoPlay=1',
        'md5': '969e343d9244778cb29acec608e53640',
        'info_dict': {
            'id': 'VOD00041610',
            'ext': 'mp4',
            'title': '花千骨第1集',
            'thumbnail': r're:https?://.*\.jpg$',
            'description': 'md5:c7017aa144c87467c4fb2909c4b05d6f',
            'episode_number': 1,
        },
        'params': {
            'noplaylist': True,
        },
        'skip': 'Georestricted to Taiwan',
    }, {
        'url': 'https://www.litv.tv/promo/miyuezhuan/?content_id=VOD00044841&',
        'md5': '88322ea132f848d6e3e18b32a832b918',
        'info_dict': {
            'id': 'VOD00044841',
            'ext': 'mp4',
            'title': '芈月傳第1集　霸星芈月降世楚國',
            'description': '楚威王二年，太史令唐昧夜觀星象，發現霸星即將現世。王后得知霸星的預言後，想盡辦法不讓孩子順利出生，幸得莒姬相護化解危機。沒想到眾人期待下出生的霸星卻是位公主，楚威王對此失望至極。楚王后命人將女嬰丟棄河中，居然奇蹟似的被少司命像攔下，楚威王認為此女非同凡響，為她取名芈月。',
        },
        'skip': 'Georestricted to Taiwan',
    }]

    def _extract_playlist(self, season_list, video_id, program_info, prompt=True):
        episode_title = program_info['title']
        content_id = season_list['contentId']

        if prompt:
            self.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (content_id, video_id))

        all_episodes = [
            self.url_result(smuggle_url(
                self._URL_TEMPLATE % (program_info['contentType'], episode['contentId']),
                {'force_noplaylist': True}))  # To prevent infinite recursion
            for episode in season_list['episode']]

        return self.playlist_result(all_episodes, content_id, episode_title)

    def _real_extract(self, url):
        url, data = unsmuggle_url(url, {})

        video_id = self._match_id(url)

        noplaylist = self._downloader.params.get('noplaylist')
        noplaylist_prompt = True
        if 'force_noplaylist' in data:
            noplaylist = data['force_noplaylist']
            noplaylist_prompt = False

        webpage = self._download_webpage(url, video_id)

        program_info = self._parse_json(self._search_regex(
            r'var\s+programInfo\s*=\s*([^;]+)', webpage, 'VOD data', default='{}'),
            video_id)

        season_list = list(program_info.get('seasonList', {}).values())
        if season_list:
            if not noplaylist:
                return self._extract_playlist(
                    season_list[0], video_id, program_info,
                    prompt=noplaylist_prompt)

            if noplaylist_prompt:
                self.to_screen('Downloading just video %s because of --no-playlist' % video_id)

        # In browsers `getMainUrl` request is always issued. Usually this
        # endpoint gives the same result as the data embedded in the webpage.
        # If georestricted, there are no embedded data, so an extra request is
        # necessary to get the error code
        if 'assetId' not in program_info:
            program_info = self._download_json(
                'https://www.litv.tv/vod/ajax/getProgramInfo', video_id,
                query={'contentId': video_id},
                headers={'Accept': 'application/json'})
        video_data = self._parse_json(self._search_regex(
            r'uiHlsUrl\s*=\s*testBackendData\(([^;]+)\);',
            webpage, 'video data', default='{}'), video_id)
        if not video_data:
            payload = {
                'assetId': program_info['assetId'],
                'watchDevices': program_info['watchDevices'],
                'contentType': program_info['contentType'],
            }
            video_data = self._download_json(
                'https://www.litv.tv/vod/getMainUrl', video_id,
                data=json.dumps(payload).encode('utf-8'),
                headers={'Content-Type': 'application/json'})

        if not video_data.get('fullpath'):
            error_msg = video_data.get('errorMessage')
            if error_msg == 'vod.error.outsideregionerror':
                self.raise_geo_restricted('This video is available in Taiwan only')
            if error_msg:
                raise ExtractorError('%s said: %s' % (self.IE_NAME, error_msg), expected=True)
            raise ExtractorError('Unexpected result from %s' % self.IE_NAME)

        formats = self._extract_m3u8_formats(
            video_data['fullpath'], video_id, ext='mp4',
            entry_protocol='m3u8_native', m3u8_id='hls')
        for a_format in formats:
            # LiTV HLS segments doesn't like compressions
            a_format.setdefault('http_headers', {})['Youtubedl-no-compression'] = True

        title = program_info['title'] + program_info.get('secondaryMark', '')
        description = program_info.get('description')
        thumbnail = program_info.get('imageFile')
        categories = [item['name'] for item in program_info.get('category', [])]
        episode = int_or_none(program_info.get('episode'))

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'categories': categories,
            'episode_number': episode,
        }


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

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import int_or_none

class LiveJournalIE(InfoExtractor):
    _VALID_URL = r'https?://(?:[^.]+\.)?livejournal\.com/video/album/\d+.+?\bid=(?P<id>\d+)'
    _TEST = {
        'url': 'https://andrei-bt.livejournal.com/video/album/407/?mode=view&id=51272',
        'md5': 'adaf018388572ced8a6f301ace49d4b2',
        'info_dict': {
            'id': '1263729',
            'ext': 'mp4',
            'title': 'Истребители против БПЛА',
            'upload_date': '20190624',
            'timestamp': 1561406715,
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        record = self._parse_json(self._search_regex(
            r'Site\.page\s*=\s*({.+?});', webpage,
            'page data'), video_id)['video']['record']
        storage_id = compat_str(record['storageid'])
        title = record.get('name')
        if title:
            # remove filename extension(.mp4, .mov, etc...)
            title = title.rsplit('.', 1)[0]
        return {
            '_type': 'url_transparent',
            'id': video_id,
            'title': title,
            'thumbnail': record.get('thumbnail'),
            'timestamp': int_or_none(record.get('timecreate')),
            'url': 'eagleplatform:vc.videos.livejournal.com:' + storage_id,
            'ie_key': 'EaglePlatform',
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import int_or_none

class LiveLeakIE(InfoExtractor):
    _VALID_URL = r'https?://(?:\w+\.)?liveleak\.com/view\?.*?\b[it]=(?P<id>[\w_]+)'
    _TESTS = [{
        'url': 'http://www.liveleak.com/view?i=757_1364311680',
        'md5': '0813c2430bea7a46bf13acf3406992f4',
        'info_dict': {
            'id': '757_1364311680',
            'ext': 'mp4',
            'description': 'extremely bad day for this guy..!',
            'uploader': 'ljfriel2',
            'title': 'Most unlucky car accident',
            'thumbnail': r're:^https?://.*\.jpg$'
        }
    }, {
        'url': 'http://www.liveleak.com/view?i=f93_1390833151',
        'md5': 'd3f1367d14cc3c15bf24fbfbe04b9abf',
        'info_dict': {
            'id': 'f93_1390833151',
            'ext': 'mp4',
            'description': 'German Television Channel NDR does an exclusive interview with Edward Snowden.\r\nUploaded on LiveLeak cause German Television thinks the rest of the world isn\'t intereseted in Edward Snowden.',
            'uploader': 'ARD_Stinkt',
            'title': 'German Television does first Edward Snowden Interview (ENGLISH)',
            'thumbnail': r're:^https?://.*\.jpg$'
        }
    }, {
        # Prochan embed
        'url': 'http://www.liveleak.com/view?i=4f7_1392687779',
        'md5': '42c6d97d54f1db107958760788c5f48f',
        'info_dict': {
            'id': '4f7_1392687779',
            'ext': 'mp4',
            'description': "The guy with the cigarette seems amazingly nonchalant about the whole thing...  I really hope my friends' reactions would be a bit stronger.\r\n\r\nAction-go to 0:55.",
            'uploader': 'CapObveus',
            'title': 'Man is Fatally Struck by Reckless Car While Packing up a Moving Truck',
            'age_limit': 18,
        },
        'skip': 'Video is dead',
    }, {
        # Covers https://github.com/ytdl-org/youtube-dl/pull/5983
        # Multiple resolutions
        'url': 'http://www.liveleak.com/view?i=801_1409392012',
        'md5': 'c3a449dbaca5c0d1825caecd52a57d7b',
        'info_dict': {
            'id': '801_1409392012',
            'ext': 'mp4',
            'description': 'Happened on 27.7.2014. \r\nAt 0:53 you can see people still swimming at near beach.',
            'uploader': 'bony333',
            'title': 'Crazy Hungarian tourist films close call waterspout in Croatia',
            'thumbnail': r're:^https?://.*\.jpg$'
        }
    }, {
        # Covers https://github.com/ytdl-org/youtube-dl/pull/10664#issuecomment-247439521
        'url': 'http://m.liveleak.com/view?i=763_1473349649',
        'add_ie': ['Youtube'],
        'info_dict': {
            'id': '763_1473349649',
            'ext': 'mp4',
            'title': 'Reporters and public officials ignore epidemic of black on asian violence in Sacramento | Colin Flaherty',
            'description': 'Colin being the warrior he is and showing the injustice Asians in Sacramento are being subjected to.',
            'uploader': 'Ziz',
            'upload_date': '20160908',
            'uploader_id': 'UCEbta5E_jqlZmEJsriTEtnw'
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.liveleak.com/view?i=677_1439397581',
        'info_dict': {
            'id': '677_1439397581',
            'title': 'Fuel Depot in China Explosion caught on video',
        },
        'playlist_count': 3,
    }, {
        'url': 'https://www.liveleak.com/view?t=HvHi_1523016227',
        'only_matching': True,
    }, {
        # No original video
        'url': 'https://www.liveleak.com/view?t=C26ZZ_1558612804',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r'<iframe[^>]+src="(https?://(?:\w+\.)?liveleak\.com/ll_embed\?[^"]*[ift]=[\w_]+[^"]+)"',
            webpage)

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

        video_title = self._og_search_title(webpage).replace('LiveLeak.com -', '').strip()
        video_description = self._og_search_description(webpage)
        video_uploader = self._html_search_regex(
            r'By:.*?(\w+)</a>', webpage, 'uploader', fatal=False)
        age_limit = int_or_none(self._search_regex(
            r'you confirm that you are ([0-9]+) years and over.',
            webpage, 'age limit', default=None))
        video_thumbnail = self._og_search_thumbnail(webpage)

        entries = self._parse_html5_media_entries(url, webpage, video_id)
        if not entries:
            # Maybe an embed?
            embed_url = self._search_regex(
                r'<iframe[^>]+src="((?:https?:)?//(?:www\.)?(?:prochan|youtube)\.com/embed[^"]+)"',
                webpage, 'embed URL')
            return {
                '_type': 'url_transparent',
                'url': embed_url,
                'id': video_id,
                'title': video_title,
                'description': video_description,
                'uploader': video_uploader,
                'age_limit': age_limit,
            }

        for idx, info_dict in enumerate(entries):
            formats = []
            for a_format in info_dict['formats']:
                if not a_format.get('height'):
                    a_format['height'] = int_or_none(self._search_regex(
                        r'([0-9]+)p\.mp4', a_format['url'], 'height label',
                        default=None))
                formats.append(a_format)

                # Removing '.*.mp4' gives the raw video, which is essentially
                # the same video without the LiveLeak logo at the top (see
                # https://github.com/ytdl-org/youtube-dl/pull/4768)
                orig_url = re.sub(r'\.mp4\.[^.]+', '', a_format['url'])
                if a_format['url'] != orig_url:
                    format_id = a_format.get('format_id')
                    format_id = 'original' + ('-' + format_id if format_id else '')
                    if self._is_valid_url(orig_url, video_id, format_id):
                        formats.append({
                            'format_id': format_id,
                            'url': orig_url,
                            'preference': 1,
                        })
            self._sort_formats(formats)
            info_dict['formats'] = formats

            # Don't append entry ID for one-video pages to keep backward compatibility
            if len(entries) > 1:
                info_dict['id'] = '%s_%s' % (video_id, idx + 1)
            else:
                info_dict['id'] = video_id

            info_dict.update({
                'title': video_title,
                'description': video_description,
                'uploader': video_uploader,
                'age_limit': age_limit,
                'thumbnail': video_thumbnail,
            })

        return self.playlist_result(entries, video_id, video_title)

class LiveLeakEmbedIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?liveleak\.com/ll_embed\?.*?\b(?P<kind>[ift])=(?P<id>[\w_]+)'

    # See generic.py for actual test cases
    _TESTS = [{
        'url': 'https://www.liveleak.com/ll_embed?i=874_1459135191',
        'only_matching': True,
    }, {
        'url': 'https://www.liveleak.com/ll_embed?f=ab065df993c1',
        'only_matching': True,
    }]

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

        if kind == 'f':
            webpage = self._download_webpage(url, video_id)
            liveleak_url = self._search_regex(
                r'(?:logourl\s*:\s*|window\.open\()(?P<q1>[\'"])(?P<url>%s)(?P=q1)' % LiveLeakIE._VALID_URL,
                webpage, 'LiveLeak URL', group='url')
        else:
            liveleak_url = 'http://www.liveleak.com/view?%s=%s' % (kind, video_id)

        return self.url_result(liveleak_url, ie=LiveLeakIE.ie_key())


code:
from __future__ import unicode_literals

import re
import itertools

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    find_xpath_attr,
    xpath_attr,
    xpath_with_ns,
    xpath_text,
    orderedSet,
    update_url_query,
    int_or_none,
    float_or_none,
    parse_iso8601,
    determine_ext,
)

class LivestreamIE(InfoExtractor):
    IE_NAME = 'livestream'
    _VALID_URL = r'https?://(?:new\.)?livestream\.com/(?:accounts/(?P<account_id>\d+)|(?P<account_name>[^/]+))/(?:events/(?P<event_id>\d+)|(?P<event_name>[^/]+))(?:/videos/(?P<id>\d+))?'
    _TESTS = [{
        'url': 'http://new.livestream.com/CoheedandCambria/WebsterHall/videos/4719370',
        'md5': '53274c76ba7754fb0e8d072716f2292b',
        'info_dict': {
            'id': '4719370',
            'ext': 'mp4',
            'title': 'Live from Webster Hall NYC',
            'timestamp': 1350008072,
            'upload_date': '20121012',
            'duration': 5968.0,
            'like_count': int,
            'view_count': int,
            'thumbnail': r're:^http://.*\.jpg$'
        }
    }, {
        'url': 'http://new.livestream.com/tedx/cityenglish',
        'info_dict': {
            'title': 'TEDCity2.0 (English)',
            'id': '2245590',
        },
        'playlist_mincount': 4,
    }, {
        'url': 'http://new.livestream.com/chess24/tatasteelchess',
        'info_dict': {
            'title': 'Tata Steel Chess',
            'id': '3705884',
        },
        'playlist_mincount': 60,
    }, {
        'url': 'https://new.livestream.com/accounts/362/events/3557232/videos/67864563/player?autoPlay=false&height=360&mute=false&width=640',
        'only_matching': True,
    }, {
        'url': 'http://livestream.com/bsww/concacafbeachsoccercampeonato2015',
        'only_matching': True,
    }]
    _API_URL_TEMPLATE = 'http://livestream.com/api/accounts/%s/events/%s'

    def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None):
        base_ele = find_xpath_attr(
            smil, self._xpath_ns('.//meta', namespace), 'name', 'httpBase')
        base = base_ele.get('content') if base_ele is not None else 'http://livestreamvod-f.akamaihd.net/'

        formats = []
        video_nodes = smil.findall(self._xpath_ns('.//video', namespace))

        for vn in video_nodes:
            tbr = int_or_none(vn.attrib.get('system-bitrate'), 1000)
            furl = (
                update_url_query(compat_urlparse.urljoin(base, vn.attrib['src']), {
                    'v': '3.0.3',
                    'fp': 'WIN% 14,0,0,145',
                }))
            if 'clipBegin' in vn.attrib:
                furl += '&ssek=' + vn.attrib['clipBegin']
            formats.append({
                'url': furl,
                'format_id': 'smil_%d' % tbr,
                'ext': 'flv',
                'tbr': tbr,
                'preference': -1000,
            })
        return formats

    def _extract_video_info(self, video_data):
        video_id = compat_str(video_data['id'])

        FORMAT_KEYS = (
            ('sd', 'progressive_url'),
            ('hd', 'progressive_url_hd'),
        )

        formats = []
        for format_id, key in FORMAT_KEYS:
            video_url = video_data.get(key)
            if video_url:
                ext = determine_ext(video_url)
                if ext == 'm3u8':
                    continue
                bitrate = int_or_none(self._search_regex(
                    r'(\d+)\.%s' % ext, video_url, 'bitrate', default=None))
                formats.append({
                    'url': video_url,
                    'format_id': format_id,
                    'tbr': bitrate,
                    'ext': ext,
                })

        smil_url = video_data.get('smil_url')
        if smil_url:
            formats.extend(self._extract_smil_formats(smil_url, video_id, fatal=False))

        m3u8_url = video_data.get('m3u8_url')
        if m3u8_url:
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, video_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False))

        f4m_url = video_data.get('f4m_url')
        if f4m_url:
            formats.extend(self._extract_f4m_formats(
                f4m_url, video_id, f4m_id='hds', fatal=False))
        self._sort_formats(formats)

        comments = [{
            'author_id': comment.get('author_id'),
            'author': comment.get('author', {}).get('full_name'),
            'id': comment.get('id'),
            'text': comment['text'],
            'timestamp': parse_iso8601(comment.get('created_at')),
        } for comment in video_data.get('comments', {}).get('data', [])]

        return {
            'id': video_id,
            'formats': formats,
            'title': video_data['caption'],
            'description': video_data.get('description'),
            'thumbnail': video_data.get('thumbnail_url'),
            'duration': float_or_none(video_data.get('duration'), 1000),
            'timestamp': parse_iso8601(video_data.get('publish_at')),
            'like_count': video_data.get('likes', {}).get('total'),
            'comment_count': video_data.get('comments', {}).get('total'),
            'view_count': video_data.get('views'),
            'comments': comments,
        }

    def _extract_stream_info(self, stream_info):
        broadcast_id = compat_str(stream_info['broadcast_id'])
        is_live = stream_info.get('is_live')

        formats = []
        smil_url = stream_info.get('play_url')
        if smil_url:
            formats.extend(self._extract_smil_formats(smil_url, broadcast_id))

        m3u8_url = stream_info.get('m3u8_url')
        if m3u8_url:
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, broadcast_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False))

        rtsp_url = stream_info.get('rtsp_url')
        if rtsp_url:
            formats.append({
                'url': rtsp_url,
                'format_id': 'rtsp',
            })
        self._sort_formats(formats)

        return {
            'id': broadcast_id,
            'formats': formats,
            'title': self._live_title(stream_info['stream_title']) if is_live else stream_info['stream_title'],
            'thumbnail': stream_info.get('thumbnail_url'),
            'is_live': is_live,
        }

    def _extract_event(self, event_data):
        event_id = compat_str(event_data['id'])
        account_id = compat_str(event_data['owner_account_id'])
        feed_root_url = self._API_URL_TEMPLATE % (account_id, event_id) + '/feed.json'

        stream_info = event_data.get('stream_info')
        if stream_info:
            return self._extract_stream_info(stream_info)

        last_video = None
        entries = []
        for i in itertools.count(1):
            if last_video is None:
                info_url = feed_root_url
            else:
                info_url = '{root}?&id={id}&newer=-1&type=video'.format(
                    root=feed_root_url, id=last_video)
            videos_info = self._download_json(
                info_url, event_id, 'Downloading page {0}'.format(i))['data']
            videos_info = [v['data'] for v in videos_info if v['type'] == 'video']
            if not videos_info:
                break
            for v in videos_info:
                v_id = compat_str(v['id'])
                entries.append(self.url_result(
                    'http://livestream.com/accounts/%s/events/%s/videos/%s' % (account_id, event_id, v_id),
                    'Livestream', v_id, v.get('caption')))
            last_video = videos_info[-1]['id']
        return self.playlist_result(entries, event_id, event_data['full_name'])

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')
        event = mobj.group('event_id') or mobj.group('event_name')
        account = mobj.group('account_id') or mobj.group('account_name')
        api_url = self._API_URL_TEMPLATE % (account, event)
        if video_id:
            video_data = self._download_json(
                api_url + '/videos/%s' % video_id, video_id)
            return self._extract_video_info(video_data)
        else:
            event_data = self._download_json(api_url, video_id)
            return self._extract_event(event_data)

# The original version of Livestream uses a different system
class LivestreamOriginalIE(InfoExtractor):
    IE_NAME = 'livestream:original'
    _VALID_URL = r'''(?x)https?://original\.livestream\.com/
        (?P<user>[^/\?#]+)(?:/(?P<type>video|folder)
        (?:(?:\?.*?Id=|/)(?P<id>.*?)(&|$))?)?
        '''
    _TESTS = [{
        'url': 'http://original.livestream.com/dealbook/video?clipId=pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
        'info_dict': {
            'id': 'pla_8aa4a3f1-ba15-46a4-893b-902210e138fb',
            'ext': 'mp4',
            'title': 'Spark 1 (BitCoin) with Cameron Winklevoss & Tyler Winklevoss of Winklevoss Capital',
            'duration': 771.301,
            'view_count': int,
        },
    }, {
        'url': 'https://original.livestream.com/newplay/folder?dirId=a07bf706-d0e4-4e75-a747-b021d84f2fd3',
        'info_dict': {
            'id': 'a07bf706-d0e4-4e75-a747-b021d84f2fd3',
        },
        'playlist_mincount': 4,
    }, {
        # live stream
        'url': 'http://original.livestream.com/znsbahamas',
        'only_matching': True,
    }]

    def _extract_video_info(self, user, video_id):
        api_url = 'http://x%sx.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id=%s' % (user, video_id)
        info = self._download_xml(api_url, video_id)

        item = info.find('channel').find('item')
        title = xpath_text(item, 'title')
        media_ns = {'media': 'http://search.yahoo.com/mrss'}
        thumbnail_url = xpath_attr(
            item, xpath_with_ns('media:thumbnail', media_ns), 'url')
        duration = float_or_none(xpath_attr(
            item, xpath_with_ns('media:content', media_ns), 'duration'))
        ls_ns = {'ls': 'http://api.channel.livestream.com/2.0'}
        view_count = int_or_none(xpath_text(
            item, xpath_with_ns('ls:viewsCount', ls_ns)))

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail_url,
            'duration': duration,
            'view_count': view_count,
        }

    def _extract_video_formats(self, video_data, video_id):
        formats = []

        progressive_url = video_data.get('progressiveUrl')
        if progressive_url:
            formats.append({
                'url': progressive_url,
                'format_id': 'http',
            })

        m3u8_url = video_data.get('httpUrl')
        if m3u8_url:
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, video_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False))

        rtsp_url = video_data.get('rtspUrl')
        if rtsp_url:
            formats.append({
                'url': rtsp_url,
                'format_id': 'rtsp',
            })

        self._sort_formats(formats)
        return formats

    def _extract_folder(self, url, folder_id):
        webpage = self._download_webpage(url, folder_id)
        paths = orderedSet(re.findall(
            r'''(?x)(?:
                <li\s+class="folder">\s*<a\s+href="|
                <a\s+href="(?=https?://livestre\.am/)
            )([^"]+)"''', webpage))

        entries = [{
            '_type': 'url',
            'url': compat_urlparse.urljoin(url, p),
        } for p in paths]

        return self.playlist_result(entries, folder_id)

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        user = mobj.group('user')
        url_type = mobj.group('type')
        content_id = mobj.group('id')
        if url_type == 'folder':
            return self._extract_folder(url, content_id)
        else:
            # this url is used on mobile devices
            stream_url = 'http://x%sx.api.channel.livestream.com/3.0/getstream.json' % user
            info = {}
            if content_id:
                stream_url += '?id=%s' % content_id
                info = self._extract_video_info(user, content_id)
            else:
                content_id = user
                webpage = self._download_webpage(url, content_id)
                info = {
                    'title': self._og_search_title(webpage),
                    'description': self._og_search_description(webpage),
                    'thumbnail': self._search_regex(r'channelLogo\.src\s*=\s*"([^"]+)"', webpage, 'thumbnail', None),
                }
            video_data = self._download_json(stream_url, content_id)
            is_live = video_data.get('isLive')
            info.update({
                'id': content_id,
                'title': self._live_title(info['title']) if is_live else info['title'],
                'formats': self._extract_video_formats(video_data, content_id),
                'is_live': is_live,
            })
            return info

# The server doesn't support HEAD request, the generic extractor can't detect
# the redirection
class LivestreamShortenerIE(InfoExtractor):
    IE_NAME = 'livestream:shortener'
    IE_DESC = False  # Do not list
    _VALID_URL = r'https?://livestre\.am/(?P<id>.+)'

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

        return self.url_result(self._og_search_url(webpage))


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

import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    compat_str,
    int_or_none,
    parse_iso8601,
)

class LnkGoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?lnk(?:go)?\.(?:alfa\.)?lt/(?:visi-video/[^/]+|video)/(?P<id>[A-Za-z0-9-]+)(?:/(?P<episode_id>\d+))?'
    _TESTS = [{
        'url': 'http://www.lnkgo.lt/visi-video/aktualai-pratesimas/ziurek-putka-trys-klausimai',
        'info_dict': {
            'id': '10809',
            'ext': 'mp4',
            'title': "Put'ka: Trys Klausimai",
            'upload_date': '20161216',
            'description': 'Seniai matytas Put’ka užduoda tris klausimėlius. Pabandykime surasti atsakymus.',
            'age_limit': 18,
            'duration': 117,
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1481904000,
        },
        'params': {
            'skip_download': True,  # HLS download
        },
    }, {
        'url': 'http://lnkgo.alfa.lt/visi-video/aktualai-pratesimas/ziurek-nerdas-taiso-kompiuteri-2',
        'info_dict': {
            'id': '10467',
            'ext': 'mp4',
            'title': 'Nėrdas: Kompiuterio Valymas',
            'upload_date': '20150113',
            'description': 'md5:7352d113a242a808676ff17e69db6a69',
            'age_limit': 18,
            'duration': 346,
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1421164800,
        },
        'params': {
            'skip_download': True,  # HLS download
        },
    }, {
        'url': 'https://lnk.lt/video/neigalieji-tv-bokste/37413',
        'only_matching': True,
    }]
    _AGE_LIMITS = {
        'N-7': 7,
        'N-14': 14,
        'S': 18,
    }
    _M3U8_TEMPL = 'https://vod.lnk.lt/lnk_vod/lnk/lnk/%s:%s/playlist.m3u8%s'

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

        video_info = self._download_json(
            'https://lnk.lt/api/main/video-page/%s/%s/false' % (display_id, video_id or '0'),
            display_id)['videoConfig']['videoInfo']

        video_id = compat_str(video_info['id'])
        title = video_info['title']
        prefix = 'smil' if video_info.get('isQualityChangeAvailable') else 'mp4'
        formats = self._extract_m3u8_formats(
            self._M3U8_TEMPL % (prefix, video_info['videoUrl'], video_info.get('secureTokenParams') or ''),
            video_id, 'mp4', 'm3u8_native')
        self._sort_formats(formats)

        poster_image = video_info.get('posterImage')

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'formats': formats,
            'thumbnail': 'https://lnk.lt/all-images/' + poster_image if poster_image else None,
            'duration': int_or_none(video_info.get('duration')),
            'description': clean_html(video_info.get('htmlDescription')),
            'age_limit': self._AGE_LIMITS.get(video_info.get('pgRating'), 0),
            'timestamp': parse_iso8601(video_info.get('airDate')),
            'view_count': int_or_none(video_info.get('viewsCount')),
        }


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

import re

from .common import InfoExtractor

class LocalNews8IE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?localnews8\.com/(?:[^/]+/)*(?P<display_id>[^/]+)/(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://www.localnews8.com/news/rexburg-business-turns-carbon-fiber-scraps-into-wedding-rings/35183304',
        'md5': 'be4d48aea61aa2bde7be2ee47691ad20',
        'info_dict': {
            'id': '35183304',
            'display_id': 'rexburg-business-turns-carbon-fiber-scraps-into-wedding-rings',
            'ext': 'mp4',
            'title': 'Rexburg business turns carbon fiber scraps into wedding ring',
            'description': 'The process was first invented by Lamborghini and less than a dozen companies around the world use it.',
            'duration': 153,
            'timestamp': 1441844822,
            'upload_date': '20150910',
            'uploader_id': 'api',
        }
    }

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

        webpage = self._download_webpage(url, display_id)

        partner_id = self._search_regex(
            r'partnerId\s*[:=]\s*(["\'])(?P<id>\d+)\1',
            webpage, 'partner id', group='id')
        kaltura_id = self._search_regex(
            r'videoIdString\s*[:=]\s*(["\'])kaltura:(?P<id>[0-9a-z_]+)\1',
            webpage, 'videl id', group='id')

        return {
            '_type': 'url_transparent',
            'url': 'kaltura:%s:%s' % (partner_id, kaltura_id),
            'ie_key': 'Kaltura',
            'id': video_id,
            'display_id': display_id,
        }


code:
from __future__ import unicode_literals

import re

from .nuevo import NuevoBaseIE

class LoveHomePornIE(NuevoBaseIE):
    _VALID_URL = r'https?://(?:www\.)?lovehomeporn\.com/video/(?P<id>\d+)(?:/(?P<display_id>[^/?#&]+))?'
    _TEST = {
        'url': 'http://lovehomeporn.com/video/48483/stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick#menu',
        'info_dict': {
            'id': '48483',
            'display_id': 'stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick',
            'ext': 'mp4',
            'title': 'Stunning busty brunette girlfriend sucking and riding a big dick',
            'age_limit': 18,
            'duration': 238.47,
        },
        'params': {
            'skip_download': True,
        }
    }

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

        info = self._extract_nuevo(
            'http://lovehomeporn.com/media/nuevo/config.php?key=%s' % video_id,
            video_id)
        info.update({
            'display_id': display_id,
            'age_limit': 18
        })
        return info


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

import re

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

class LRTIE(InfoExtractor):
    IE_NAME = 'lrt.lt'
    _VALID_URL = r'https?://(?:www\.)?lrt\.lt/mediateka/irasas/(?P<id>[0-9]+)'
    _TESTS = [{
        # m3u8 download
        'url': 'http://www.lrt.lt/mediateka/irasas/54391/',
        'md5': 'fe44cf7e4ab3198055f2c598fc175cb0',
        'info_dict': {
            'id': '54391',
            'ext': 'mp4',
            'title': 'Septynios Kauno dienos',
            'description': 'md5:24d84534c7dc76581e59f5689462411a',
            'duration': 1783,
            'view_count': int,
            'like_count': int,
        },
    }, {
        # direct mp3 download
        'url': 'http://www.lrt.lt/mediateka/irasas/1013074524/',
        'md5': '389da8ca3cad0f51d12bed0c844f6a0a',
        'info_dict': {
            'id': '1013074524',
            'ext': 'mp3',
            'title': 'Kita tema 2016-09-05 15:05',
            'description': 'md5:1b295a8fc7219ed0d543fc228c931fb5',
            'duration': 3008,
            'view_count': int,
            'like_count': int,
        },
    }]

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

        title = remove_end(self._og_search_title(webpage), ' - LRT')

        formats = []
        for _, file_url in re.findall(
                r'file\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage):
            ext = determine_ext(file_url)
            if ext not in ('m3u8', 'mp3'):
                continue
            # mp3 served as m3u8 produces stuttered media file
            if ext == 'm3u8' and '.mp3' in file_url:
                continue
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    file_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    fatal=False))
            elif ext == 'mp3':
                formats.append({
                    'url': file_url,
                    'vcodec': 'none',
                })
        self._sort_formats(formats)

        thumbnail = self._og_search_thumbnail(webpage)
        description = self._og_search_description(webpage)
        duration = parse_duration(self._search_regex(
            r'var\s+record_len\s*=\s*(["\'])(?P<duration>[0-9]+:[0-9]+:[0-9]+)\1',
            webpage, 'duration', default=None, group='duration'))

        view_count = int_or_none(self._html_search_regex(
            r'<div[^>]+class=(["\']).*?record-desc-seen.*?\1[^>]*>(?P<count>.+?)</div>',
            webpage, 'view count', fatal=False, group='count'))
        like_count = int_or_none(self._search_regex(
            r'<span[^>]+id=(["\'])flikesCount.*?\1>(?P<count>\d+)<',
            webpage, 'like count', fatal=False, group='count'))

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'thumbnail': thumbnail,
            'description': description,
            'duration': duration,
            'view_count': view_count,
            'like_count': like_count,
        }