diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py index bf34ae6b7..be28b0057 100644 --- a/youtube_dl/extractor/extractors.py +++ b/youtube_dl/extractor/extractors.py @@ -868,6 +868,7 @@ from .platzi import ( PlatziIE, PlatziCourseIE, ) +from .playerpl import PlayerPlIE from .playfm import PlayFMIE from .playplustv import PlayPlusTVIE from .plays import PlaysTVIE diff --git a/youtube_dl/extractor/playerpl.py b/youtube_dl/extractor/playerpl.py new file mode 100644 index 000000000..ffa729a7e --- /dev/null +++ b/youtube_dl/extractor/playerpl.py @@ -0,0 +1,168 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import re + +from .common import InfoExtractor +from ..utils import ( + parse_duration, + merge_dicts, + ExtractorError, +) + +""" +The extractor can download free videos from https://player.pl +The code is done from scratch, although +the original idea and _REQUEST_VIDEO_URL is borrowed from +https://github.com/zacny/voddownloader/ +""" + + +class PlayerPlIE(InfoExtractor): + IE_NAME = 'TVN' + IE_DESC = 'Polska komercyjna stacja telewizyjna' + _VALID_URL = r'https?://player\.pl/.*,(?P[0-9]+)' + _TESTS = [ + { + 'url': 'https://player.pl/programy-online/projekt-lady-odcinki,4554/odcinek-12,S05E12,186499', + 'info_dict': { + 'id': '186499', + 'ext': 'mp4', + 'title': 'Projekt Lady-S05E12', + 'description': 'md5:b322b3929f4e00bca010fe646122795a' + } + }, + { + 'url': 'https://player.pl/filmy-online/kino-2020---uszy-zyrafy,178673', + 'info_dict': { + 'id': '178673', + 'ext': 'mp4', + 'title': u'KINO 2020 - Uszy \u017byrafy-S00E00', + 'description': 'md5:852f5196a2304a3d941c1716ca71f1de' + } + } + ] + _REQUEST_VIDEO_URL = r'https://player.pl/api/?platform=ConnectedTV&terminal=Panasonic&format=json' \ + '&authKey=064fda5ab26dc1dd936f5c6e84b7d3c2&v=3.1&m=getItem&id={videoid}' + _SERIES_URL_RE = r'https?://player.pl/.*-odcinki,(?P[0-9]+)' + _SERIES_EPISODE_URL_RE = r'https?://player.pl/.*-odcinki,[0-9]+/odcinek-.*,(?P[0-9]+)' + _FILM_URL_RE = r'https?:\/\/player.pl\/.*(?[0-9]+)' + _FORMATS_QUALITY_COMMON = {"ext": "mp4", + "format_id": "mp4_h2644_aac", + "acodec": "aac", + "fps": 25, + "vcodec": "h264", + "container": "mp4", + "language": "pl" + } + _FORMATS_QUALITY = [ + (r'Bardzo niska', {"width": 320, "height": 240, "tbr": 238}), + (r'Niska', {"width": 512, "height": 384, "tbr": 417}), + (r'.*rednia', {"width": 640, "height": 480, "tbr": 596}), + (r'Standard', {"width": 720, "height": 576, "tbr": 794}), + (r'Wysoka', {"width": 720, "height": 576, "tbr": 1191}), + (r'Bardzo wysoka', {"width": 1280, "height": 720, "tbr": 1786}), + (r'HD', {"width": 1280, "height": 720, "tbr": 2776}) + ] + + def __get_format(self, fname): + for i, x in enumerate(self._FORMATS_QUALITY): + if re.match(x[0], fname): + return i, merge_dicts(x[1], self._FORMATS_QUALITY_COMMON) + self._downloader.report_warning("Unknown format '{}'".format(fname)) + return None + + def __extract_single_url(self, video_id): + info = self._download_json(self._REQUEST_VIDEO_URL.format(videoid=video_id), video_id).get("item") + formats = [] + for vfm in info.get("videos").get("main").get("video_content"): + fm = self.__get_format(vfm.get("profile_name")) + if fm is not None: + fm[1].update({"url": vfm.get("url")}) + formats.append(fm) + formats.sort() + formats = [x[1] for x in formats] + title = info.get("title") + if title is None or title == '': + title = "{}-S{:02d}E{:02d}".format(info.get("serie_title"), info.get("season"), info.get("episode")) + + return { + 'id': "id" + video_id, + 'title': title, + 'display_id': info.get("id"), + 'description': info.get("lead"), + 'duration': parse_duration(info.get('run_time')), + 'formats': formats, + 'series': info.get("serie_title"), + 'season': info.get("season_title"), + 'season_number': info.get("season"), + 'episode': info.get("episode_title"), + 'episode_number': info.get("episode"), + } + + def __extract_series(self, video_id): + int_video_resp = self._download_json( + r'https://player.pl/playerapi/item/translate', None, + query={ + "programId": video_id, + "4K": "true", + "platform": "BROWSER" + }) + + if int_video_resp.get('code') == "VOD_NOT_EXISTS" or str(int_video_resp.get("externalProgramId")) != video_id: + raise ExtractorError('%s said: Video is not exists. Please check the URL' % self.IE_NAME, expected=True) + + int_prg_id = int(int_video_resp.get('id')) + + vod_prefix = r'https://player.pl/playerapi/product/vod/serial/' + + seasons = self._download_json( + vod_prefix + r'{}/season/list'.format(int_prg_id), None, + query={ + '4K': True, + 'platform': 'BROWSER' + }) + seasons.sort(key=lambda k: k['number']) + for sz in seasons: + sid = sz.get("id") + sn = sz.get('number') + eps = self._download_json( + vod_prefix + r'{prgid}/season/{sid}/episode/list'.format(prgid=int_prg_id, sid=sid), None, + query={ + '4K': True, + 'platform': 'BROWSER' + }) + lst = [] + for e in eps: + if e["type"] == "EPISODE" and e["season"]["id"] == sid: + lst.append((sn, e.get("episode"), e.get("externalArticleId"))) + + lst.sort() + result = [] + for l in lst: + rs = self.__extract_single_url(str(l[2])) + if rs["formats"] is not None and len(rs["formats"]) > 0: + result.append(rs) + else: + self._downloader.report_warning("Video '{}' is unavailable.".format(rs["title"])) + + s_info = self._download_json( + vod_prefix + r'{}'.format(int_prg_id), None, + query={ + '4K': True, + 'platform': 'BROWSER' + }) + + return self.playlist_result(result, playlist_id=video_id, + playlist_title=s_info["title"], playlist_description=s_info["lead"]) + + def _real_extract(self, url): + for m in [re.match(self._SERIES_EPISODE_URL_RE, url), re.match(self._FILM_URL_RE, url)]: + if m: + return self.__extract_single_url(m.group("id")) + + m = re.match(self._SERIES_URL_RE, url) + if m: + return self.__extract_series(m.group("id")) + + return None