commit
c33de004e1
62 changed files with 1722 additions and 1127 deletions
@ -1,7 +1,9 @@ |
|||||||
include README.md |
include README.md |
||||||
include test/*.py |
include LICENSE |
||||||
include test/*.json |
include AUTHORS |
||||||
|
include ChangeLog |
||||||
include youtube-dl.bash-completion |
include youtube-dl.bash-completion |
||||||
include youtube-dl.fish |
include youtube-dl.fish |
||||||
include youtube-dl.1 |
include youtube-dl.1 |
||||||
recursive-include docs Makefile conf.py *.rst |
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 |
from __future__ import unicode_literals |
||||||
|
|
||||||
__version__ = '2017.12.02' |
__version__ = '2017.12.23' |
||||||
|
Loading…
Reference in new issue