commit
c33de004e1
62 changed files with 1722 additions and 1127 deletions
@ -1,7 +1,9 @@ |
||||
include README.md |
||||
include test/*.py |
||||
include test/*.json |
||||
include LICENSE |
||||
include AUTHORS |
||||
include ChangeLog |
||||
include youtube-dl.bash-completion |
||||
include youtube-dl.fish |
||||
include youtube-dl.1 |
||||
recursive-include docs Makefile conf.py *.rst |
||||
recursive-include test * |
||||
|
@ -0,0 +1,78 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
import datetime |
||||
import hashlib |
||||
import hmac |
||||
|
||||
from .common import InfoExtractor |
||||
from ..compat import compat_urllib_parse_urlencode |
||||
|
||||
|
||||
class AWSIE(InfoExtractor): |
||||
_AWS_ALGORITHM = 'AWS4-HMAC-SHA256' |
||||
_AWS_REGION = 'us-east-1' |
||||
|
||||
def _aws_execute_api(self, aws_dict, video_id, query=None): |
||||
query = query or {} |
||||
amz_date = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ') |
||||
date = amz_date[:8] |
||||
headers = { |
||||
'Accept': 'application/json', |
||||
'Host': self._AWS_PROXY_HOST, |
||||
'X-Amz-Date': amz_date, |
||||
} |
||||
session_token = aws_dict.get('session_token') |
||||
if session_token: |
||||
headers['X-Amz-Security-Token'] = session_token |
||||
headers['X-Api-Key'] = self._AWS_API_KEY |
||||
|
||||
def aws_hash(s): |
||||
return hashlib.sha256(s.encode('utf-8')).hexdigest() |
||||
|
||||
# Task 1: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
||||
canonical_querystring = compat_urllib_parse_urlencode(query) |
||||
canonical_headers = '' |
||||
for header_name, header_value in headers.items(): |
||||
canonical_headers += '%s:%s\n' % (header_name.lower(), header_value) |
||||
signed_headers = ';'.join([header.lower() for header in headers.keys()]) |
||||
canonical_request = '\n'.join([ |
||||
'GET', |
||||
aws_dict['uri'], |
||||
canonical_querystring, |
||||
canonical_headers, |
||||
signed_headers, |
||||
aws_hash('') |
||||
]) |
||||
|
||||
# Task 2: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html |
||||
credential_scope_list = [date, self._AWS_REGION, 'execute-api', 'aws4_request'] |
||||
credential_scope = '/'.join(credential_scope_list) |
||||
string_to_sign = '\n'.join([self._AWS_ALGORITHM, amz_date, credential_scope, aws_hash(canonical_request)]) |
||||
|
||||
# Task 3: http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html |
||||
def aws_hmac(key, msg): |
||||
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256) |
||||
|
||||
def aws_hmac_digest(key, msg): |
||||
return aws_hmac(key, msg).digest() |
||||
|
||||
def aws_hmac_hexdigest(key, msg): |
||||
return aws_hmac(key, msg).hexdigest() |
||||
|
||||
k_signing = ('AWS4' + aws_dict['secret_key']).encode('utf-8') |
||||
for value in credential_scope_list: |
||||
k_signing = aws_hmac_digest(k_signing, value) |
||||
|
||||
signature = aws_hmac_hexdigest(k_signing, string_to_sign) |
||||
|
||||
# Task 4: http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html |
||||
headers['Authorization'] = ', '.join([ |
||||
'%s Credential=%s/%s' % (self._AWS_ALGORITHM, aws_dict['access_key'], credential_scope), |
||||
'SignedHeaders=%s' % signed_headers, |
||||
'Signature=%s' % signature, |
||||
]) |
||||
|
||||
return self._download_json( |
||||
'https://%s%s%s' % (self._AWS_PROXY_HOST, aws_dict['uri'], '?' + canonical_querystring if canonical_querystring else ''), |
||||
video_id, headers=headers) |
@ -0,0 +1,133 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
from .common import InfoExtractor |
||||
from ..utils import ( |
||||
clean_html, |
||||
extract_attributes, |
||||
float_or_none, |
||||
int_or_none, |
||||
try_get, |
||||
) |
||||
|
||||
|
||||
class EllenTubeBaseIE(InfoExtractor): |
||||
def _extract_data_config(self, webpage, video_id): |
||||
details = self._search_regex( |
||||
r'(<[^>]+\bdata-component=(["\'])[Dd]etails.+?></div>)', webpage, |
||||
'details') |
||||
return self._parse_json( |
||||
extract_attributes(details)['data-config'], video_id) |
||||
|
||||
def _extract_video(self, data, video_id): |
||||
title = data['title'] |
||||
|
||||
formats = [] |
||||
duration = None |
||||
for entry in data.get('media'): |
||||
if entry.get('id') == 'm3u8': |
||||
formats = self._extract_m3u8_formats( |
||||
entry['url'], video_id, 'mp4', |
||||
entry_protocol='m3u8_native', m3u8_id='hls') |
||||
duration = int_or_none(entry.get('duration')) |
||||
break |
||||
self._sort_formats(formats) |
||||
|
||||
def get_insight(kind): |
||||
return int_or_none(try_get( |
||||
data, lambda x: x['insight']['%ss' % kind])) |
||||
|
||||
return { |
||||
'extractor_key': EllenTubeIE.ie_key(), |
||||
'id': video_id, |
||||
'title': title, |
||||
'description': data.get('description'), |
||||
'duration': duration, |
||||
'thumbnail': data.get('thumbnail'), |
||||
'timestamp': float_or_none(data.get('publishTime'), scale=1000), |
||||
'view_count': get_insight('view'), |
||||
'like_count': get_insight('like'), |
||||
'formats': formats, |
||||
} |
||||
|
||||
|
||||
class EllenTubeIE(EllenTubeBaseIE): |
||||
_VALID_URL = r'''(?x) |
||||
(?: |
||||
ellentube:| |
||||
https://api-prod\.ellentube\.com/ellenapi/api/item/ |
||||
) |
||||
(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}) |
||||
''' |
||||
_TESTS = [{ |
||||
'url': 'https://api-prod.ellentube.com/ellenapi/api/item/0822171c-3829-43bf-b99f-d77358ae75e3', |
||||
'md5': '2fabc277131bddafdd120e0fc0f974c9', |
||||
'info_dict': { |
||||
'id': '0822171c-3829-43bf-b99f-d77358ae75e3', |
||||
'ext': 'mp4', |
||||
'title': 'Ellen Meets Las Vegas Survivors Jesus Campos and Stephen Schuck', |
||||
'description': 'md5:76e3355e2242a78ad9e3858e5616923f', |
||||
'thumbnail': r're:^https?://.+?', |
||||
'duration': 514, |
||||
'timestamp': 1508505120, |
||||
'upload_date': '20171020', |
||||
'view_count': int, |
||||
'like_count': int, |
||||
} |
||||
}, { |
||||
'url': 'ellentube:734a3353-f697-4e79-9ca9-bfc3002dc1e0', |
||||
'only_matching': True, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
video_id = self._match_id(url) |
||||
data = self._download_json( |
||||
'https://api-prod.ellentube.com/ellenapi/api/item/%s' % video_id, |
||||
video_id) |
||||
return self._extract_video(data, video_id) |
||||
|
||||
|
||||
class EllenTubeVideoIE(EllenTubeBaseIE): |
||||
_VALID_URL = r'https?://(?:www\.)?ellentube\.com/video/(?P<id>.+?)\.html' |
||||
_TEST = { |
||||
'url': 'https://www.ellentube.com/video/ellen-meets-las-vegas-survivors-jesus-campos-and-stephen-schuck.html', |
||||
'only_matching': True, |
||||
} |
||||
|
||||
def _real_extract(self, url): |
||||
display_id = self._match_id(url) |
||||
webpage = self._download_webpage(url, display_id) |
||||
video_id = self._extract_data_config(webpage, display_id)['id'] |
||||
return self.url_result( |
||||
'ellentube:%s' % video_id, ie=EllenTubeIE.ie_key(), |
||||
video_id=video_id) |
||||
|
||||
|
||||
class EllenTubePlaylistIE(EllenTubeBaseIE): |
||||
_VALID_URL = r'https?://(?:www\.)?ellentube\.com/(?:episode|studios)/(?P<id>.+?)\.html' |
||||
_TESTS = [{ |
||||
'url': 'https://www.ellentube.com/episode/dax-shepard-jordan-fisher-haim.html', |
||||
'info_dict': { |
||||
'id': 'dax-shepard-jordan-fisher-haim', |
||||
'title': "Dax Shepard, 'DWTS' Team Jordan Fisher & Lindsay Arnold, HAIM", |
||||
'description': 'md5:bfc982194dabb3f4e325e43aa6b2e21c', |
||||
}, |
||||
'playlist_count': 6, |
||||
}, { |
||||
'url': 'https://www.ellentube.com/studios/macey-goes-rving0.html', |
||||
'only_matching': True, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
display_id = self._match_id(url) |
||||
webpage = self._download_webpage(url, display_id) |
||||
data = self._extract_data_config(webpage, display_id)['data'] |
||||
feed = self._download_json( |
||||
'https://api-prod.ellentube.com/ellenapi/api/feed/?%s' |
||||
% data['filter'], display_id) |
||||
entries = [ |
||||
self._extract_video(elem, elem['id']) |
||||
for elem in feed if elem.get('type') == 'VIDEO' and elem.get('id')] |
||||
return self.playlist_result( |
||||
entries, display_id, data.get('title'), |
||||
clean_html(data.get('description'))) |
@ -1,101 +0,0 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
from .common import InfoExtractor |
||||
from .kaltura import KalturaIE |
||||
from ..utils import NO_DEFAULT |
||||
|
||||
|
||||
class EllenTVIE(InfoExtractor): |
||||
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)' |
||||
_TESTS = [{ |
||||
'url': 'http://www.ellentv.com/videos/0-ipq1gsai/', |
||||
'md5': '4294cf98bc165f218aaa0b89e0fd8042', |
||||
'info_dict': { |
||||
'id': '0_ipq1gsai', |
||||
'ext': 'mov', |
||||
'title': 'Fast Fingers of Fate', |
||||
'description': 'md5:3539013ddcbfa64b2a6d1b38d910868a', |
||||
'timestamp': 1428035648, |
||||
'upload_date': '20150403', |
||||
'uploader_id': 'batchUser', |
||||
}, |
||||
}, { |
||||
# not available via http://widgets.ellentube.com/ |
||||
'url': 'http://www.ellentv.com/videos/1-szkgu2m2/', |
||||
'info_dict': { |
||||
'id': '1_szkgu2m2', |
||||
'ext': 'flv', |
||||
'title': "Ellen's Amazingly Talented Audience", |
||||
'description': 'md5:86ff1e376ff0d717d7171590e273f0a5', |
||||
'timestamp': 1255140900, |
||||
'upload_date': '20091010', |
||||
'uploader_id': 'ellenkaltura@gmail.com', |
||||
}, |
||||
'params': { |
||||
'skip_download': True, |
||||
}, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
video_id = self._match_id(url) |
||||
|
||||
URLS = ('http://widgets.ellentube.com/videos/%s' % video_id, url) |
||||
|
||||
for num, url_ in enumerate(URLS, 1): |
||||
webpage = self._download_webpage( |
||||
url_, video_id, fatal=num == len(URLS)) |
||||
|
||||
default = NO_DEFAULT if num == len(URLS) else None |
||||
|
||||
partner_id = self._search_regex( |
||||
r"var\s+partnerId\s*=\s*'([^']+)", webpage, 'partner id', |
||||
default=default) |
||||
|
||||
kaltura_id = self._search_regex( |
||||
[r'id="kaltura_player_([^"]+)"', |
||||
r"_wb_entry_id\s*:\s*'([^']+)", |
||||
r'data-kaltura-entry-id="([^"]+)'], |
||||
webpage, 'kaltura id', default=default) |
||||
|
||||
if partner_id and kaltura_id: |
||||
break |
||||
|
||||
return self.url_result('kaltura:%s:%s' % (partner_id, kaltura_id), KalturaIE.ie_key()) |
||||
|
||||
|
||||
class EllenTVClipsIE(InfoExtractor): |
||||
IE_NAME = 'EllenTV:clips' |
||||
_VALID_URL = r'https?://(?:www\.)?ellentv\.com/episodes/(?P<id>[a-z0-9_-]+)' |
||||
_TEST = { |
||||
'url': 'http://www.ellentv.com/episodes/meryl-streep-vanessa-hudgens/', |
||||
'info_dict': { |
||||
'id': 'meryl-streep-vanessa-hudgens', |
||||
'title': 'Meryl Streep, Vanessa Hudgens', |
||||
}, |
||||
'playlist_mincount': 5, |
||||
} |
||||
|
||||
def _real_extract(self, url): |
||||
playlist_id = self._match_id(url) |
||||
|
||||
webpage = self._download_webpage(url, playlist_id) |
||||
playlist = self._extract_playlist(webpage, playlist_id) |
||||
|
||||
return { |
||||
'_type': 'playlist', |
||||
'id': playlist_id, |
||||
'title': self._og_search_title(webpage), |
||||
'entries': self._extract_entries(playlist) |
||||
} |
||||
|
||||
def _extract_playlist(self, webpage, playlist_id): |
||||
json_string = self._search_regex(r'playerView.addClips\(\[\{(.*?)\}\]\);', webpage, 'json') |
||||
return self._parse_json('[{' + json_string + '}]', playlist_id) |
||||
|
||||
def _extract_entries(self, playlist): |
||||
return [ |
||||
self.url_result( |
||||
'kaltura:%s:%s' % (item['kaltura_partner_id'], item['kaltura_entry_id']), |
||||
KalturaIE.ie_key(), video_id=item['kaltura_entry_id']) |
||||
for item in playlist] |
@ -1,261 +0,0 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
import re |
||||
|
||||
from .common import InfoExtractor |
||||
from ..compat import compat_str |
||||
from ..utils import ( |
||||
ExtractorError, |
||||
determine_ext, |
||||
int_or_none, |
||||
parse_iso8601, |
||||
parse_duration, |
||||
remove_start, |
||||
) |
||||
|
||||
|
||||
class NowTVBaseIE(InfoExtractor): |
||||
_VIDEO_FIELDS = ( |
||||
'id', 'title', 'free', 'geoblocked', 'articleLong', 'articleShort', |
||||
'broadcastStartDate', 'seoUrl', 'duration', 'files', |
||||
'format.defaultImage169Format', 'format.defaultImage169Logo') |
||||
|
||||
def _extract_video(self, info, display_id=None): |
||||
video_id = compat_str(info['id']) |
||||
|
||||
files = info['files'] |
||||
if not files: |
||||
if info.get('geoblocked', False): |
||||
raise ExtractorError( |
||||
'Video %s is not available from your location due to geo restriction' % video_id, |
||||
expected=True) |
||||
if not info.get('free', True): |
||||
raise ExtractorError( |
||||
'Video %s is not available for free' % video_id, expected=True) |
||||
|
||||
formats = [] |
||||
for item in files['items']: |
||||
if determine_ext(item['path']) != 'f4v': |
||||
continue |
||||
app, play_path = remove_start(item['path'], '/').split('/', 1) |
||||
formats.append({ |
||||
'url': 'rtmpe://fms.rtl.de', |
||||
'app': app, |
||||
'play_path': 'mp4:%s' % play_path, |
||||
'ext': 'flv', |
||||
'page_url': 'http://rtlnow.rtl.de', |
||||
'player_url': 'http://cdn.static-fra.de/now/vodplayer.swf', |
||||
'tbr': int_or_none(item.get('bitrate')), |
||||
}) |
||||
self._sort_formats(formats) |
||||
|
||||
title = info['title'] |
||||
description = info.get('articleLong') or info.get('articleShort') |
||||
timestamp = parse_iso8601(info.get('broadcastStartDate'), ' ') |
||||
duration = parse_duration(info.get('duration')) |
||||
|
||||
f = info.get('format', {}) |
||||
thumbnail = f.get('defaultImage169Format') or f.get('defaultImage169Logo') |
||||
|
||||
return { |
||||
'id': video_id, |
||||
'display_id': display_id or info.get('seoUrl'), |
||||
'title': title, |
||||
'description': description, |
||||
'thumbnail': thumbnail, |
||||
'timestamp': timestamp, |
||||
'duration': duration, |
||||
'formats': formats, |
||||
} |
||||
|
||||
|
||||
class NowTVIE(NowTVBaseIE): |
||||
_WORKING = False |
||||
_VALID_URL = r'https?://(?:www\.)?nowtv\.(?:de|at|ch)/(?:rtl|rtl2|rtlnitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/(?:(?:list/[^/]+|jahr/\d{4}/\d{1,2})/)?(?P<id>[^/]+)/(?:player|preview)' |
||||
|
||||
_TESTS = [{ |
||||
# rtl |
||||
'url': 'http://www.nowtv.de/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/player', |
||||
'info_dict': { |
||||
'id': '203519', |
||||
'display_id': 'bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit', |
||||
'ext': 'flv', |
||||
'title': 'Inka Bause stellt die neuen Bauern vor', |
||||
'description': 'md5:e234e1ed6d63cf06be5c070442612e7e', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1432580700, |
||||
'upload_date': '20150525', |
||||
'duration': 2786, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
# rtl2 |
||||
'url': 'http://www.nowtv.de/rtl2/berlin-tag-nacht/berlin-tag-nacht-folge-934/player', |
||||
'info_dict': { |
||||
'id': '203481', |
||||
'display_id': 'berlin-tag-nacht/berlin-tag-nacht-folge-934', |
||||
'ext': 'flv', |
||||
'title': 'Berlin - Tag & Nacht (Folge 934)', |
||||
'description': 'md5:c85e88c2e36c552dfe63433bc9506dd0', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1432666800, |
||||
'upload_date': '20150526', |
||||
'duration': 2641, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
# rtlnitro |
||||
'url': 'http://www.nowtv.de/rtlnitro/alarm-fuer-cobra-11-die-autobahnpolizei/hals-und-beinbruch-2014-08-23-21-10-00/player', |
||||
'info_dict': { |
||||
'id': '165780', |
||||
'display_id': 'alarm-fuer-cobra-11-die-autobahnpolizei/hals-und-beinbruch-2014-08-23-21-10-00', |
||||
'ext': 'flv', |
||||
'title': 'Hals- und Beinbruch', |
||||
'description': 'md5:b50d248efffe244e6f56737f0911ca57', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1432415400, |
||||
'upload_date': '20150523', |
||||
'duration': 2742, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
# superrtl |
||||
'url': 'http://www.nowtv.de/superrtl/medicopter-117/angst/player', |
||||
'info_dict': { |
||||
'id': '99205', |
||||
'display_id': 'medicopter-117/angst', |
||||
'ext': 'flv', |
||||
'title': 'Angst!', |
||||
'description': 'md5:30cbc4c0b73ec98bcd73c9f2a8c17c4e', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1222632900, |
||||
'upload_date': '20080928', |
||||
'duration': 3025, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
# ntv |
||||
'url': 'http://www.nowtv.de/ntv/ratgeber-geld/thema-ua-der-erste-blick-die-apple-watch/player', |
||||
'info_dict': { |
||||
'id': '203521', |
||||
'display_id': 'ratgeber-geld/thema-ua-der-erste-blick-die-apple-watch', |
||||
'ext': 'flv', |
||||
'title': 'Thema u.a.: Der erste Blick: Die Apple Watch', |
||||
'description': 'md5:4312b6c9d839ffe7d8caf03865a531af', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1432751700, |
||||
'upload_date': '20150527', |
||||
'duration': 1083, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
# vox |
||||
'url': 'http://www.nowtv.de/vox/der-hundeprofi/buero-fall-chihuahua-joel/player', |
||||
'info_dict': { |
||||
'id': '128953', |
||||
'display_id': 'der-hundeprofi/buero-fall-chihuahua-joel', |
||||
'ext': 'flv', |
||||
'title': "Büro-Fall / Chihuahua 'Joel'", |
||||
'description': 'md5:e62cb6bf7c3cc669179d4f1eb279ad8d', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1432408200, |
||||
'upload_date': '20150523', |
||||
'duration': 3092, |
||||
}, |
||||
'params': { |
||||
# rtmp download |
||||
'skip_download': True, |
||||
}, |
||||
}, { |
||||
'url': 'http://www.nowtv.de/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/preview', |
||||
'only_matching': True, |
||||
}, { |
||||
'url': 'http://www.nowtv.at/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/preview?return=/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit', |
||||
'only_matching': True, |
||||
}, { |
||||
'url': 'http://www.nowtv.de/rtl2/echtzeit/list/aktuell/schnelles-geld-am-ende-der-welt/player', |
||||
'only_matching': True, |
||||
}, { |
||||
'url': 'http://www.nowtv.de/rtl2/zuhause-im-glueck/jahr/2015/11/eine-erschuetternde-diagnose/player', |
||||
'only_matching': True, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
mobj = re.match(self._VALID_URL, url) |
||||
display_id = '%s/%s' % (mobj.group('show_id'), mobj.group('id')) |
||||
|
||||
info = self._download_json( |
||||
'https://api.nowtv.de/v3/movies/%s?fields=%s' |
||||
% (display_id, ','.join(self._VIDEO_FIELDS)), display_id) |
||||
|
||||
return self._extract_video(info, display_id) |
||||
|
||||
|
||||
class NowTVListIE(NowTVBaseIE): |
||||
_VALID_URL = r'https?://(?:www\.)?nowtv\.(?:de|at|ch)/(?:rtl|rtl2|rtlnitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/list/(?P<id>[^?/#&]+)$' |
||||
|
||||
_SHOW_FIELDS = ('title', ) |
||||
_SEASON_FIELDS = ('id', 'headline', 'seoheadline', ) |
||||
|
||||
_TESTS = [{ |
||||
'url': 'http://www.nowtv.at/rtl/stern-tv/list/aktuell', |
||||
'info_dict': { |
||||
'id': '17006', |
||||
'title': 'stern TV - Aktuell', |
||||
}, |
||||
'playlist_count': 1, |
||||
}, { |
||||
'url': 'http://www.nowtv.at/rtl/das-supertalent/list/free-staffel-8', |
||||
'info_dict': { |
||||
'id': '20716', |
||||
'title': 'Das Supertalent - FREE Staffel 8', |
||||
}, |
||||
'playlist_count': 14, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
mobj = re.match(self._VALID_URL, url) |
||||
show_id = mobj.group('show_id') |
||||
season_id = mobj.group('id') |
||||
|
||||
fields = [] |
||||
fields.extend(self._SHOW_FIELDS) |
||||
fields.extend('formatTabs.%s' % field for field in self._SEASON_FIELDS) |
||||
fields.extend( |
||||
'formatTabs.formatTabPages.container.movies.%s' % field |
||||
for field in self._VIDEO_FIELDS) |
||||
|
||||
list_info = self._download_json( |
||||
'https://api.nowtv.de/v3/formats/seo?fields=%s&name=%s.php' |
||||
% (','.join(fields), show_id), |
||||
season_id) |
||||
|
||||
season = next( |
||||
season for season in list_info['formatTabs']['items'] |
||||
if season.get('seoheadline') == season_id) |
||||
|
||||
title = '%s - %s' % (list_info['title'], season['headline']) |
||||
|
||||
entries = [] |
||||
for container in season['formatTabPages']['items']: |
||||
for info in ((container.get('container') or {}).get('movies') or {}).get('items') or []: |
||||
entries.append(self._extract_video(info)) |
||||
|
||||
return self.playlist_result( |
||||
entries, compat_str(season.get('id') or season_id), title) |
@ -0,0 +1,67 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
import re |
||||
|
||||
from .brightcove import BrightcoveNewIE |
||||
from ..utils import update_url_query |
||||
|
||||
|
||||
class SevenPlusIE(BrightcoveNewIE): |
||||
IE_NAME = '7plus' |
||||
_VALID_URL = r'https?://(?:www\.)?7plus\.com\.au/(?P<path>[^?]+\?.*?\bepisode-id=(?P<id>[^&#]+))' |
||||
_TESTS = [{ |
||||
'url': 'https://7plus.com.au/BEAT?episode-id=BEAT-001', |
||||
'info_dict': { |
||||
'id': 'BEAT-001', |
||||
'ext': 'mp4', |
||||
'title': 'S1 E1 - Help / Lucy In The Sky With Diamonds', |
||||
'description': 'md5:37718bea20a8eedaca7f7361af566131', |
||||
'uploader_id': '5303576322001', |
||||
'upload_date': '20171031', |
||||
'timestamp': 1509440068, |
||||
}, |
||||
'params': { |
||||
'format': 'bestvideo', |
||||
'skip_download': True, |
||||
} |
||||
}, { |
||||
'url': 'https://7plus.com.au/UUUU?episode-id=AUMS43-001', |
||||
'only_matching': True, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
path, episode_id = re.match(self._VALID_URL, url).groups() |
||||
|
||||
media = self._download_json( |
||||
'https://videoservice.swm.digital/playback', episode_id, query={ |
||||
'appId': '7plus', |
||||
'deviceType': 'web', |
||||
'platformType': 'web', |
||||
'accountId': 5303576322001, |
||||
'referenceId': 'ref:' + episode_id, |
||||
'deliveryId': 'csai', |
||||
'videoType': 'vod', |
||||
})['media'] |
||||
|
||||
for source in media.get('sources', {}): |
||||
src = source.get('src') |
||||
if not src: |
||||
continue |
||||
source['src'] = update_url_query(src, {'rule': ''}) |
||||
|
||||
info = self._parse_brightcove_metadata(media, episode_id) |
||||
|
||||
content = self._download_json( |
||||
'https://component-cdn.swm.digital/content/' + path, |
||||
episode_id, headers={ |
||||
'market-id': 4, |
||||
}, fatal=False) or {} |
||||
for item in content.get('items', {}): |
||||
if item.get('componentData', {}).get('componentType') == 'infoPanel': |
||||
for src_key, dst_key in [('title', 'title'), ('shortSynopsis', 'description')]: |
||||
value = item.get(src_key) |
||||
if value: |
||||
info[dst_key] = value |
||||
|
||||
return info |
@ -0,0 +1,48 @@ |
||||
from __future__ import unicode_literals |
||||
|
||||
from .common import InfoExtractor |
||||
from ..utils import int_or_none |
||||
|
||||
|
||||
class StretchInternetIE(InfoExtractor): |
||||
_VALID_URL = r'https?://portal\.stretchinternet\.com/[^/]+/portal\.htm\?.*?\beventId=(?P<id>\d+)' |
||||
_TEST = { |
||||
'url': 'https://portal.stretchinternet.com/umary/portal.htm?eventId=313900&streamType=video', |
||||
'info_dict': { |
||||
'id': '313900', |
||||
'ext': 'mp4', |
||||
'title': 'Augustana (S.D.) Baseball vs University of Mary', |
||||
'description': 'md5:7578478614aae3bdd4a90f578f787438', |
||||
'timestamp': 1490468400, |
||||
'upload_date': '20170325', |
||||
} |
||||
} |
||||
|
||||
def _real_extract(self, url): |
||||
video_id = self._match_id(url) |
||||
|
||||
stream = self._download_json( |
||||
'https://neo-client.stretchinternet.com/streamservice/v1/media/stream/v%s' |
||||
% video_id, video_id) |
||||
|
||||
video_url = 'https://%s' % stream['source'] |
||||
|
||||
event = self._download_json( |
||||
'https://neo-client.stretchinternet.com/portal-ws/getEvent.json', |
||||
video_id, query={ |
||||
'clientID': 99997, |
||||
'eventID': video_id, |
||||
'token': 'asdf', |
||||
})['event'] |
||||
|
||||
title = event.get('title') or event['mobileTitle'] |
||||
description = event.get('customText') |
||||
timestamp = int_or_none(event.get('longtime')) |
||||
|
||||
return { |
||||
'id': video_id, |
||||
'title': title, |
||||
'description': description, |
||||
'timestamp': timestamp, |
||||
'url': video_url, |
||||
} |
@ -0,0 +1,175 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
import re |
||||
|
||||
from .common import InfoExtractor |
||||
from ..compat import compat_str |
||||
from ..utils import ( |
||||
ExtractorError, |
||||
parse_iso8601, |
||||
parse_duration, |
||||
update_url_query, |
||||
) |
||||
|
||||
|
||||
class TVNowBaseIE(InfoExtractor): |
||||
_VIDEO_FIELDS = ( |
||||
'id', 'title', 'free', 'geoblocked', 'articleLong', 'articleShort', |
||||
'broadcastStartDate', 'isDrm', 'duration', 'manifest.dashclear', |
||||
'format.defaultImage169Format', 'format.defaultImage169Logo') |
||||
|
||||
def _call_api(self, path, video_id, query): |
||||
return self._download_json( |
||||
'https://api.tvnow.de/v3/' + path, |
||||
video_id, query=query) |
||||
|
||||
def _extract_video(self, info, display_id): |
||||
video_id = compat_str(info['id']) |
||||
title = info['title'] |
||||
|
||||
mpd_url = info['manifest']['dashclear'] |
||||
if not mpd_url: |
||||
if info.get('isDrm'): |
||||
raise ExtractorError( |
||||
'Video %s is DRM protected' % video_id, expected=True) |
||||
if info.get('geoblocked'): |
||||
raise ExtractorError( |
||||
'Video %s is not available from your location due to geo restriction' % video_id, |
||||
expected=True) |
||||
if not info.get('free', True): |
||||
raise ExtractorError( |
||||
'Video %s is not available for free' % video_id, expected=True) |
||||
|
||||
mpd_url = update_url_query(mpd_url, {'filter': ''}) |
||||
formats = self._extract_mpd_formats(mpd_url, video_id, mpd_id='dash', fatal=False) |
||||
formats.extend(self._extract_ism_formats( |
||||
mpd_url.replace('dash.', 'hss.').replace('/.mpd', '/Manifest'), |
||||
video_id, ism_id='mss', fatal=False)) |
||||
formats.extend(self._extract_m3u8_formats( |
||||
mpd_url.replace('dash.', 'hls.').replace('/.mpd', '/.m3u8'), |
||||
video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)) |
||||
self._sort_formats(formats) |
||||
|
||||
description = info.get('articleLong') or info.get('articleShort') |
||||
timestamp = parse_iso8601(info.get('broadcastStartDate'), ' ') |
||||
duration = parse_duration(info.get('duration')) |
||||
|
||||
f = info.get('format', {}) |
||||
thumbnail = f.get('defaultImage169Format') or f.get('defaultImage169Logo') |
||||
|
||||
return { |
||||
'id': video_id, |
||||
'display_id': display_id, |
||||
'title': title, |
||||
'description': description, |
||||
'thumbnail': thumbnail, |
||||
'timestamp': timestamp, |
||||
'duration': duration, |
||||
'formats': formats, |
||||
} |
||||
|
||||
|
||||
class TVNowIE(TVNowBaseIE): |
||||
_VALID_URL = r'https?://(?:www\.)?tvnow\.(?:de|at|ch)/(?:rtl(?:2|plus)?|nitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/(?:(?:list/[^/]+|jahr/\d{4}/\d{1,2})/)?(?P<id>[^/]+)/(?:player|preview)' |
||||
|
||||
_TESTS = [{ |
||||
# rtl |
||||
'url': 'https://www.tvnow.de/rtl/alarm-fuer-cobra-11/freier-fall/player?return=/rtl', |
||||
'info_dict': { |
||||
'id': '385314', |
||||
'display_id': 'alarm-fuer-cobra-11/freier-fall', |
||||
'ext': 'mp4', |
||||
'title': 'Freier Fall', |
||||
'description': 'md5:8c2d8f727261adf7e0dc18366124ca02', |
||||
'thumbnail': r're:^https?://.*\.jpg$', |
||||
'timestamp': 1512677700, |
||||
'upload_date': '20171207', |
||||
'duration': 2862.0, |
||||
}, |
||||
}, { |
||||
# rtl2 |
||||
'url': 'https://www.tvnow.de/rtl2/armes-deutschland/episode-0008/player', |
||||
'only_matching': 'True', |
||||
}, { |
||||
# rtlnitro |
||||
'url': 'https://www.tvnow.de/nitro/alarm-fuer-cobra-11-die-autobahnpolizei/auf-eigene-faust-pilot/player', |
||||
'only_matching': 'True', |
||||
}, { |
||||
# superrtl |
||||
'url': 'https://www.tvnow.de/superrtl/die-lustigsten-schlamassel-der-welt/u-a-ketchup-effekt/player', |
||||
'only_matching': 'True', |
||||
}, { |
||||
# ntv |
||||
'url': 'https://www.tvnow.de/ntv/startup-news/goetter-in-weiss/player', |
||||
'only_matching': 'True', |
||||
}, { |
||||
# vox |
||||
'url': 'https://www.tvnow.de/vox/auto-mobil/neues-vom-automobilmarkt-2017-11-19-17-00-00/player', |
||||
'only_matching': 'True', |
||||
}, { |
||||
# rtlplus |
||||
'url': 'https://www.tvnow.de/rtlplus/op-ruft-dr-bruckner/die-vernaehte-frau/player', |
||||
'only_matching': 'True', |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
display_id = '%s/%s' % re.match(self._VALID_URL, url).groups() |
||||
|
||||
info = self._call_api( |
||||
'movies/' + display_id, display_id, query={ |
||||
'fields': ','.join(self._VIDEO_FIELDS), |
||||
}) |
||||
|
||||
return self._extract_video(info, display_id) |
||||
|
||||
|
||||
class TVNowListIE(TVNowBaseIE): |
||||
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?tvnow\.(?:de|at|ch)/(?:rtl(?:2|plus)?|nitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/)list/(?P<id>[^?/#&]+)$' |
||||
|
||||
_SHOW_FIELDS = ('title', ) |
||||
_SEASON_FIELDS = ('id', 'headline', 'seoheadline', ) |
||||
_VIDEO_FIELDS = ('id', 'headline', 'seoUrl', ) |
||||
|
||||
_TESTS = [{ |
||||
'url': 'https://www.tvnow.de/rtl/30-minuten-deutschland/list/aktuell', |
||||
'info_dict': { |
||||
'id': '28296', |
||||
'title': '30 Minuten Deutschland - Aktuell', |
||||
}, |
||||
'playlist_mincount': 1, |
||||
}] |
||||
|
||||
def _real_extract(self, url): |
||||
base_url, show_id, season_id = re.match(self._VALID_URL, url).groups() |
||||
|
||||
fields = [] |
||||
fields.extend(self._SHOW_FIELDS) |
||||
fields.extend('formatTabs.%s' % field for field in self._SEASON_FIELDS) |
||||
fields.extend( |
||||
'formatTabs.formatTabPages.container.movies.%s' % field |
||||
for field in self._VIDEO_FIELDS) |
||||
|
||||
list_info = self._call_api( |
||||
'formats/seo', season_id, query={ |
||||
'fields': ','.join(fields), |
||||
'name': show_id + '.php' |
||||
}) |
||||
|
||||
season = next( |
||||
season for season in list_info['formatTabs']['items'] |
||||
if season.get('seoheadline') == season_id) |
||||
|
||||
title = '%s - %s' % (list_info['title'], season['headline']) |
||||
|
||||
entries = [] |
||||
for container in season['formatTabPages']['items']: |
||||
for info in ((container.get('container') or {}).get('movies') or {}).get('items') or []: |
||||
seo_url = info.get('seoUrl') |
||||
if not seo_url: |
||||
continue |
||||
entries.append(self.url_result( |
||||
base_url + seo_url + '/player', 'TVNow', info.get('id'))) |
||||
|
||||
return self.playlist_result( |
||||
entries, compat_str(season.get('id') or season_id), title) |
@ -0,0 +1,103 @@ |
||||
# coding: utf-8 |
||||
from __future__ import unicode_literals |
||||
|
||||
from .common import InfoExtractor |
||||
from ..utils import ( |
||||
int_or_none, |
||||
parse_filesize, |
||||
parse_iso8601, |
||||
) |
||||
|
||||
|
||||
class UMGDeIE(InfoExtractor): |
||||
IE_NAME = 'umg:de' |
||||
IE_DESC = 'Universal Music Deutschland' |
||||
_VALID_URL = r'https?://(?:www\.)?universal-music\.de/[^/]+/videos/[^/?#]+-(?P<id>\d+)' |
||||
_TEST = { |
||||
'url': 'https://www.universal-music.de/sido/videos/jedes-wort-ist-gold-wert-457803', |
||||
'md5': 'ebd90f48c80dcc82f77251eb1902634f', |
||||
'info_dict': { |
||||
'id': '457803', |
||||
'ext': 'mp4', |
||||
'title': 'Jedes Wort ist Gold wert', |
||||
'timestamp': 1513591800, |
||||
'upload_date': '20171218', |
||||
} |
||||
} |
||||
|
||||
def _real_extract(self, url): |
||||
video_id = self._match_id(url) |
||||
video_data = self._download_json( |
||||
'https://api.universal-music.de/graphql', |
||||
video_id, query={ |
||||
'query': '''{ |
||||
universalMusic(channel:16) { |
||||
video(id:%s) { |
||||
headline |
||||
formats { |
||||
formatId |
||||
url |
||||
type |
||||
width |
||||
height |
||||
mimeType |
||||
fileSize |
||||
} |
||||
duration |
||||
createdDate |
||||
} |
||||
} |
||||
}''' % video_id})['data']['universalMusic']['video'] |
||||
|
||||
title = video_data['headline'] |
||||
hls_url_template = 'http://mediadelivery.universal-music-services.de/vod/mp4:autofill/storage/' + '/'.join(list(video_id)) + '/content/%s/file/playlist.m3u8' |
||||
|
||||
thumbnails = [] |
||||
formats = [] |
||||
|
||||
def add_m3u8_format(format_id): |
||||
m3u8_formats = self._extract_m3u8_formats( |
||||
hls_url_template % format_id, video_id, 'mp4', |
||||
'm3u8_native', m3u8_id='hls', fatal='False') |
||||
if m3u8_formats and m3u8_formats[0].get('height'): |
||||
formats.extend(m3u8_formats) |
||||
|
||||
for f in video_data.get('formats', []): |
||||
f_url = f.get('url') |
||||
mime_type = f.get('mimeType') |
||||
if not f_url or mime_type == 'application/mxf': |
||||
continue |
||||
fmt = { |
||||
'url': f_url, |
||||
'width': int_or_none(f.get('width')), |
||||
'height': int_or_none(f.get('height')), |
||||
'filesize': parse_filesize(f.get('fileSize')), |
||||
} |
||||
f_type = f.get('type') |
||||
if f_type == 'Image': |
||||
thumbnails.append(fmt) |
||||
elif f_type == 'Video': |
||||
format_id = f.get('formatId') |
||||
if format_id: |
||||
fmt['format_id'] = format_id |
||||
if mime_type == 'video/mp4': |
||||
add_m3u8_format(format_id) |
||||
urlh = self._request_webpage(f_url, video_id, fatal=False) |
||||
if urlh: |
||||
first_byte = urlh.read(1) |
||||
if first_byte not in (b'F', b'\x00'): |
||||
continue |
||||
formats.append(fmt) |
||||
if not formats: |
||||
for format_id in (867, 836, 940): |
||||
add_m3u8_format(format_id) |
||||
self._sort_formats(formats, ('width', 'height', 'filesize', 'tbr')) |
||||
|
||||
return { |
||||
'id': video_id, |
||||
'title': title, |
||||
'duration': int_or_none(video_data.get('duration')), |
||||
'timestamp': parse_iso8601(video_data.get('createdDate'), ' '), |
||||
'thumbnails': thumbnails, |
||||
'formats': formats, |
||||
} |
@ -1,3 +1,3 @@ |
||||
from __future__ import unicode_literals |
||||
|
||||
__version__ = '2017.12.02' |
||||
__version__ = '2017.12.23' |
||||
|
Loading…
Reference in new issue