mirror of https://github.com/blackjack4494/yt-dlc
Merge branch 'master' of github.com:rg3/youtube-dl into weibo
commit
c33de004e1
@ -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