mirror of
https://github.com/l1ving/youtube-dl
synced 2025-02-03 14:55:35 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
ee4098bbcf
@ -1,8 +1,11 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..aes import aes_cbc_decrypt
|
||||
@ -12,9 +15,12 @@ from ..compat import (
|
||||
)
|
||||
from ..utils import (
|
||||
bytes_to_intlist,
|
||||
bytes_to_long,
|
||||
ExtractorError,
|
||||
float_or_none,
|
||||
intlist_to_bytes,
|
||||
long_to_bytes,
|
||||
pkcs1pad,
|
||||
srt_subtitles_timecode,
|
||||
strip_or_none,
|
||||
urljoin,
|
||||
@ -35,6 +41,7 @@ class ADNIE(InfoExtractor):
|
||||
}
|
||||
}
|
||||
_BASE_URL = 'http://animedigitalnetwork.fr'
|
||||
_RSA_KEY = (0xc35ae1e4356b65a73b551493da94b8cb443491c0aa092a357a5aee57ffc14dda85326f42d716e539a34542a0d3f363adf16c5ec222d713d5997194030ee2e4f0d1fb328c01a81cf6868c090d50de8e169c6b13d1675b9eeed1cbc51e1fffca9b38af07f37abd790924cd3bee59d0257cfda4fe5f3f0534877e21ce5821447d1b, 65537)
|
||||
|
||||
def _get_subtitles(self, sub_path, video_id):
|
||||
if not sub_path:
|
||||
@ -42,16 +49,14 @@ class ADNIE(InfoExtractor):
|
||||
|
||||
enc_subtitles = self._download_webpage(
|
||||
urljoin(self._BASE_URL, sub_path),
|
||||
video_id, fatal=False, headers={
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0',
|
||||
})
|
||||
video_id, fatal=False)
|
||||
if not enc_subtitles:
|
||||
return None
|
||||
|
||||
# http://animedigitalnetwork.fr/components/com_vodvideo/videojs/adn-vjs.min.js
|
||||
dec_subtitles = intlist_to_bytes(aes_cbc_decrypt(
|
||||
bytes_to_intlist(compat_b64decode(enc_subtitles[24:])),
|
||||
bytes_to_intlist(b'\xc8\x6e\x06\xbc\xbe\xc6\x49\xf5\x88\x0d\xc8\x47\xc4\x27\x0c\x60'),
|
||||
bytes_to_intlist(binascii.unhexlify(self._K + '9032ad7083106400')),
|
||||
bytes_to_intlist(compat_b64decode(enc_subtitles[:24]))
|
||||
))
|
||||
subtitles_json = self._parse_json(
|
||||
@ -112,11 +117,24 @@ class ADNIE(InfoExtractor):
|
||||
error = None
|
||||
if not links:
|
||||
links_url = player_config.get('linksurl') or options['videoUrl']
|
||||
links_data = self._download_json(urljoin(
|
||||
self._BASE_URL, links_url), video_id)
|
||||
token = options['token']
|
||||
self._K = ''.join([random.choice('0123456789abcdef') for _ in range(16)])
|
||||
message = bytes_to_intlist(json.dumps({
|
||||
'k': self._K,
|
||||
'e': 60,
|
||||
't': token,
|
||||
}))
|
||||
padded_message = intlist_to_bytes(pkcs1pad(message, 128))
|
||||
n, e = self._RSA_KEY
|
||||
encrypted_message = long_to_bytes(pow(bytes_to_long(padded_message), e, n))
|
||||
authorization = base64.b64encode(encrypted_message).decode()
|
||||
links_data = self._download_json(
|
||||
urljoin(self._BASE_URL, links_url), video_id, headers={
|
||||
'Authorization': 'Bearer ' + authorization,
|
||||
})
|
||||
links = links_data.get('links') or {}
|
||||
metas = metas or links_data.get('meta') or {}
|
||||
sub_path = sub_path or links_data.get('subtitles')
|
||||
sub_path = (sub_path or links_data.get('subtitles')) + '&token=' + token
|
||||
error = links_data.get('error')
|
||||
title = metas.get('title') or video_info['title']
|
||||
|
||||
|
@ -12,6 +12,7 @@ from ..utils import (
|
||||
float_or_none,
|
||||
get_element_by_class,
|
||||
int_or_none,
|
||||
js_to_json,
|
||||
parse_duration,
|
||||
parse_iso8601,
|
||||
try_get,
|
||||
@ -772,6 +773,17 @@ class BBCIE(BBCCoUkIE):
|
||||
# single video article embedded with data-media-vpid
|
||||
'url': 'http://www.bbc.co.uk/sport/rowing/35908187',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.bbc.co.uk/bbcthree/clip/73d0bbd0-abc3-4cea-b3c0-cdae21905eb1',
|
||||
'info_dict': {
|
||||
'id': 'p06556y7',
|
||||
'ext': 'mp4',
|
||||
'title': 'Transfers: Cristiano Ronaldo to Man Utd, Arsenal to spend?',
|
||||
'description': 'md5:4b7dfd063d5a789a1512e99662be3ddd',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
}
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
@ -994,6 +1006,36 @@ class BBCIE(BBCCoUkIE):
|
||||
'subtitles': subtitles,
|
||||
}
|
||||
|
||||
bbc3_config = self._parse_json(
|
||||
self._search_regex(
|
||||
r'(?s)bbcthreeConfig\s*=\s*({.+?})\s*;\s*<', webpage,
|
||||
'bbcthree config', default='{}'),
|
||||
playlist_id, transform_source=js_to_json, fatal=False)
|
||||
if bbc3_config:
|
||||
bbc3_playlist = try_get(
|
||||
bbc3_config, lambda x: x['payload']['content']['bbcMedia']['playlist'],
|
||||
dict)
|
||||
if bbc3_playlist:
|
||||
playlist_title = bbc3_playlist.get('title') or playlist_title
|
||||
thumbnail = bbc3_playlist.get('holdingImageURL')
|
||||
entries = []
|
||||
for bbc3_item in bbc3_playlist['items']:
|
||||
programme_id = bbc3_item.get('versionID')
|
||||
if not programme_id:
|
||||
continue
|
||||
formats, subtitles = self._download_media_selector(programme_id)
|
||||
self._sort_formats(formats)
|
||||
entries.append({
|
||||
'id': programme_id,
|
||||
'title': playlist_title,
|
||||
'thumbnail': thumbnail,
|
||||
'timestamp': timestamp,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
})
|
||||
return self.playlist_result(
|
||||
entries, playlist_id, playlist_title, playlist_description)
|
||||
|
||||
def extract_all(pattern):
|
||||
return list(filter(None, map(
|
||||
lambda s: self._parse_json(s, playlist_id, fatal=False),
|
||||
|
@ -31,7 +31,8 @@ class ChaturbateIE(InfoExtractor):
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
webpage = self._download_webpage(
|
||||
url, video_id, headers=self.geo_verification_headers())
|
||||
|
||||
m3u8_urls = []
|
||||
|
||||
|
@ -74,7 +74,14 @@ class SafariBaseIE(InfoExtractor):
|
||||
class SafariIE(SafariBaseIE):
|
||||
IE_NAME = 'safari'
|
||||
IE_DESC = 'safaribooksonline.com online video'
|
||||
_VALID_URL = r'https?://(?:www\.)?safaribooksonline\.com/library/view/[^/]+/(?P<course_id>[^/]+)/(?P<part>[^/?#&]+)\.html'
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:www\.)?safaribooksonline\.com/
|
||||
(?:
|
||||
library/view/[^/]+/(?P<course_id>[^/]+)/(?P<part>[^/?\#&]+)\.html|
|
||||
videos/[^/]+/[^/]+/(?P<reference_id>[^-]+-[^/?\#&]+)
|
||||
)
|
||||
'''
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://www.safaribooksonline.com/library/view/hadoop-fundamentals-livelessons/9780133392838/part00.html',
|
||||
@ -94,22 +101,41 @@ class SafariIE(SafariBaseIE):
|
||||
}, {
|
||||
'url': 'https://www.safaribooksonline.com/library/view/learning-path-red/9780134664057/RHCE_Introduction.html',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.safaribooksonline.com/videos/python-programming-language/9780134217314/9780134217314-PYMC_13_00',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
_PARTNER_ID = '1926081'
|
||||
_UICONF_ID = '29375172'
|
||||
|
||||
def _real_extract(self, url):
|
||||
mobj = re.match(self._VALID_URL, url)
|
||||
video_id = '%s/%s' % (mobj.group('course_id'), mobj.group('part'))
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
reference_id = self._search_regex(
|
||||
r'data-reference-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura reference id', group='id')
|
||||
partner_id = self._search_regex(
|
||||
r'data-partner-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura widget id', group='id')
|
||||
ui_id = self._search_regex(
|
||||
r'data-ui-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura uiconf id', group='id')
|
||||
reference_id = mobj.group('reference_id')
|
||||
if reference_id:
|
||||
video_id = reference_id
|
||||
partner_id = self._PARTNER_ID
|
||||
ui_id = self._UICONF_ID
|
||||
else:
|
||||
video_id = '%s-%s' % (mobj.group('course_id'), mobj.group('part'))
|
||||
|
||||
webpage, urlh = self._download_webpage_handle(url, video_id)
|
||||
|
||||
mobj = re.match(self._VALID_URL, urlh.geturl())
|
||||
reference_id = mobj.group('reference_id')
|
||||
if not reference_id:
|
||||
reference_id = self._search_regex(
|
||||
r'data-reference-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura reference id', group='id')
|
||||
partner_id = self._search_regex(
|
||||
r'data-partner-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura widget id', default=self._PARTNER_ID,
|
||||
group='id')
|
||||
ui_id = self._search_regex(
|
||||
r'data-ui-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
|
||||
webpage, 'kaltura uiconf id', default=self._UICONF_ID,
|
||||
group='id')
|
||||
|
||||
query = {
|
||||
'wid': '_%s' % partner_id,
|
||||
@ -159,10 +185,15 @@ class SafariCourseIE(SafariBaseIE):
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://
|
||||
(?:
|
||||
(?:www\.)?safaribooksonline\.com/(?:library/view/[^/]+|api/v1/book)|
|
||||
(?:www\.)?safaribooksonline\.com/
|
||||
(?:
|
||||
library/view/[^/]+|
|
||||
api/v1/book|
|
||||
videos/[^/]+
|
||||
)|
|
||||
techbus\.safaribooksonline\.com
|
||||
)
|
||||
/(?P<id>[^/]+)/?(?:[#?]|$)
|
||||
/(?P<id>[^/]+)
|
||||
'''
|
||||
|
||||
_TESTS = [{
|
||||
@ -179,8 +210,16 @@ class SafariCourseIE(SafariBaseIE):
|
||||
}, {
|
||||
'url': 'http://techbus.safaribooksonline.com/9780134426365',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.safaribooksonline.com/videos/python-programming-language/9780134217314',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)
|
||||
else super(SafariCourseIE, cls).suitable(url))
|
||||
|
||||
def _real_extract(self, url):
|
||||
course_id = self._match_id(url)
|
||||
|
||||
|
@ -510,6 +510,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/IconaPop',
|
||||
'license': 'Standard YouTube License',
|
||||
'creator': 'Icona Pop',
|
||||
'track': 'I Love It (feat. Charli XCX)',
|
||||
'artist': 'Icona Pop',
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -528,6 +530,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/justintimberlakeVEVO',
|
||||
'license': 'Standard YouTube License',
|
||||
'creator': 'Justin Timberlake',
|
||||
'track': 'Tunnel Vision',
|
||||
'artist': 'Justin Timberlake',
|
||||
'age_limit': 18,
|
||||
}
|
||||
},
|
||||
@ -597,7 +601,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'id': 'IB3lcPjvWLA',
|
||||
'ext': 'm4a',
|
||||
'title': 'Afrojack, Spree Wilson - The Spark ft. Spree Wilson',
|
||||
'description': 'md5:12e7067fa6735a77bdcbb58cb1187d2d',
|
||||
'description': 'md5:1900ed86ee514927b9e00fbead6969a5',
|
||||
'duration': 244,
|
||||
'uploader': 'AfrojackVEVO',
|
||||
'uploader_id': 'AfrojackVEVO',
|
||||
@ -638,7 +642,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'duration': 219,
|
||||
'upload_date': '20100909',
|
||||
'uploader': 'The Amazing Atheist',
|
||||
'uploader': 'TJ Kirk',
|
||||
'uploader_id': 'TheAmazingAtheist',
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/TheAmazingAtheist',
|
||||
'license': 'Standard YouTube License',
|
||||
@ -668,10 +672,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'url': 'https://www.youtube.com/watch?v=6kLq3WMV1nU',
|
||||
'info_dict': {
|
||||
'id': '6kLq3WMV1nU',
|
||||
'ext': 'mp4',
|
||||
'ext': 'webm',
|
||||
'title': 'Dedication To My Ex (Miss That) (Lyric Video)',
|
||||
'description': 'md5:33765bb339e1b47e7e72b5490139bb41',
|
||||
'duration': 247,
|
||||
'duration': 246,
|
||||
'uploader': 'LloydVEVO',
|
||||
'uploader_id': 'LloydVEVO',
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/LloydVEVO',
|
||||
@ -733,7 +737,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_id': 'AllenMeow',
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/AllenMeow',
|
||||
'description': 'made by Wacom from Korea | 字幕&加油添醋 by TY\'s Allen | 感謝heylisa00cavey1001同學熱情提供梗及翻譯',
|
||||
'uploader': '孫艾倫',
|
||||
'uploader': '孫ᄋᄅ',
|
||||
'license': 'Standard YouTube License',
|
||||
'title': '[A-made] 變態妍字幕版 太妍 我就是這樣的人',
|
||||
},
|
||||
@ -760,7 +764,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'url': 'https://www.youtube.com/watch?v=FIl7x6_3R5Y',
|
||||
'info_dict': {
|
||||
'id': 'FIl7x6_3R5Y',
|
||||
'ext': 'mp4',
|
||||
'ext': 'webm',
|
||||
'title': 'md5:7b81415841e02ecd4313668cde88737a',
|
||||
'description': 'md5:116377fd2963b81ec4ce64b542173306',
|
||||
'duration': 220,
|
||||
@ -769,8 +773,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/dorappi2000',
|
||||
'uploader': 'dorappi2000',
|
||||
'license': 'Standard YouTube License',
|
||||
'formats': 'mincount:32',
|
||||
'formats': 'mincount:31',
|
||||
},
|
||||
'skip': 'not actual anymore',
|
||||
},
|
||||
# DASH manifest with segment_list
|
||||
{
|
||||
@ -885,7 +890,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'id': 'lsguqyKfVQg',
|
||||
'ext': 'mp4',
|
||||
'title': '{dark walk}; Loki/AC/Dishonored; collab w/Elflover21',
|
||||
'alt_title': 'Dark Walk',
|
||||
'alt_title': 'Dark Walk - Position Music',
|
||||
'description': 'md5:8085699c11dc3f597ce0410b0dcbb34a',
|
||||
'duration': 133,
|
||||
'upload_date': '20151119',
|
||||
@ -893,7 +898,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/IronSoulElf',
|
||||
'uploader': 'IronSoulElf',
|
||||
'license': 'Standard YouTube License',
|
||||
'creator': 'Todd Haberman, Daniel Law Heath & Aaron Kaplan',
|
||||
'creator': 'Todd Haberman, Daniel Law Heath and Aaron Kaplan',
|
||||
'track': 'Dark Walk - Position Music',
|
||||
'artist': 'Todd Haberman, Daniel Law Heath and Aaron Kaplan',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
@ -950,7 +957,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'description': 'md5:dda0d780d5a6e120758d1711d062a867',
|
||||
'duration': 4060,
|
||||
'upload_date': '20151119',
|
||||
'uploader': 'Bernie 2016',
|
||||
'uploader': 'Bernie Sanders',
|
||||
'uploader_id': 'UCH1dpzjCEiGAt8CXkryhkZg',
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UCH1dpzjCEiGAt8CXkryhkZg',
|
||||
'license': 'Creative Commons Attribution license (reuse allowed)',
|
||||
@ -985,6 +992,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'This video is not available.',
|
||||
},
|
||||
{
|
||||
# YouTube Red video with episode data
|
||||
@ -993,7 +1001,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'id': 'iqKdEhx-dD4',
|
||||
'ext': 'mp4',
|
||||
'title': 'Isolation - Mind Field (Ep 1)',
|
||||
'description': 'md5:8013b7ddea787342608f63a13ddc9492',
|
||||
'description': 'md5:25b78d2f64ae81719f5c96319889b736',
|
||||
'duration': 2085,
|
||||
'upload_date': '20170118',
|
||||
'uploader': 'Vsauce',
|
||||
@ -1026,7 +1034,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_id': 'UCEJYpZGqgUob0zVVEaLhvVg',
|
||||
'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UCEJYpZGqgUob0zVVEaLhvVg',
|
||||
'license': 'Standard YouTube License',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
@ -1694,128 +1701,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if 'ypc_video_rental_bar_text' in video_info and 'author' not in video_info:
|
||||
raise ExtractorError('"rental" videos not supported. See https://github.com/rg3/youtube-dl/issues/359 for more information.', expected=True)
|
||||
|
||||
# Start extracting information
|
||||
self.report_information_extraction(video_id)
|
||||
|
||||
# uploader
|
||||
video_uploader = try_get(video_info, lambda x: x['author'][0], compat_str)
|
||||
if video_uploader:
|
||||
video_uploader = compat_urllib_parse_unquote_plus(video_uploader)
|
||||
else:
|
||||
self._downloader.report_warning('unable to extract uploader name')
|
||||
|
||||
# uploader_id
|
||||
video_uploader_id = None
|
||||
video_uploader_url = None
|
||||
mobj = re.search(
|
||||
r'<link itemprop="url" href="(?P<uploader_url>https?://www\.youtube\.com/(?:user|channel)/(?P<uploader_id>[^"]+))">',
|
||||
video_webpage)
|
||||
if mobj is not None:
|
||||
video_uploader_id = mobj.group('uploader_id')
|
||||
video_uploader_url = mobj.group('uploader_url')
|
||||
else:
|
||||
self._downloader.report_warning('unable to extract uploader nickname')
|
||||
|
||||
# thumbnail image
|
||||
# We try first to get a high quality image:
|
||||
m_thumb = re.search(r'<span itemprop="thumbnail".*?href="(.*?)">',
|
||||
video_webpage, re.DOTALL)
|
||||
if m_thumb is not None:
|
||||
video_thumbnail = m_thumb.group(1)
|
||||
elif 'thumbnail_url' not in video_info:
|
||||
self._downloader.report_warning('unable to extract video thumbnail')
|
||||
video_thumbnail = None
|
||||
else: # don't panic if we can't find it
|
||||
video_thumbnail = compat_urllib_parse_unquote_plus(video_info['thumbnail_url'][0])
|
||||
|
||||
# upload date
|
||||
upload_date = self._html_search_meta(
|
||||
'datePublished', video_webpage, 'upload date', default=None)
|
||||
if not upload_date:
|
||||
upload_date = self._search_regex(
|
||||
[r'(?s)id="eow-date.*?>(.*?)</span>',
|
||||
r'(?:id="watch-uploader-info".*?>.*?|["\']simpleText["\']\s*:\s*["\'])(?:Published|Uploaded|Streamed live|Started) on (.+?)[<"\']'],
|
||||
video_webpage, 'upload date', default=None)
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
video_license = self._html_search_regex(
|
||||
r'<h4[^>]+class="title"[^>]*>\s*License\s*</h4>\s*<ul[^>]*>\s*<li>(.+?)</li',
|
||||
video_webpage, 'license', default=None)
|
||||
|
||||
m_music = re.search(
|
||||
r'''(?x)
|
||||
<h4[^>]+class="title"[^>]*>\s*Music\s*</h4>\s*
|
||||
<ul[^>]*>\s*
|
||||
<li>(?P<title>.+?)
|
||||
by (?P<creator>.+?)
|
||||
(?:
|
||||
\(.+?\)|
|
||||
<a[^>]*
|
||||
(?:
|
||||
\bhref=["\']/red[^>]*>| # drop possible
|
||||
>\s*Listen ad-free with YouTube Red # YouTube Red ad
|
||||
)
|
||||
.*?
|
||||
)?</li
|
||||
''',
|
||||
video_webpage)
|
||||
if m_music:
|
||||
video_alt_title = remove_quotes(unescapeHTML(m_music.group('title')))
|
||||
video_creator = clean_html(m_music.group('creator'))
|
||||
else:
|
||||
video_alt_title = video_creator = None
|
||||
|
||||
m_episode = re.search(
|
||||
r'<div[^>]+id="watch7-headline"[^>]*>\s*<span[^>]*>.*?>(?P<series>[^<]+)</a></b>\s*S(?P<season>\d+)\s*•\s*E(?P<episode>\d+)</span>',
|
||||
video_webpage)
|
||||
if m_episode:
|
||||
series = m_episode.group('series')
|
||||
season_number = int(m_episode.group('season'))
|
||||
episode_number = int(m_episode.group('episode'))
|
||||
else:
|
||||
series = season_number = episode_number = None
|
||||
|
||||
m_cat_container = self._search_regex(
|
||||
r'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>',
|
||||
video_webpage, 'categories', default=None)
|
||||
if m_cat_container:
|
||||
category = self._html_search_regex(
|
||||
r'(?s)<a[^<]+>(.*?)</a>', m_cat_container, 'category',
|
||||
default=None)
|
||||
video_categories = None if category is None else [category]
|
||||
else:
|
||||
video_categories = None
|
||||
|
||||
video_tags = [
|
||||
unescapeHTML(m.group('content'))
|
||||
for m in re.finditer(self._meta_regex('og:video:tag'), video_webpage)]
|
||||
|
||||
def _extract_count(count_name):
|
||||
return str_to_int(self._search_regex(
|
||||
r'-%s-button[^>]+><span[^>]+class="yt-uix-button-content"[^>]*>([\d,]+)</span>'
|
||||
% re.escape(count_name),
|
||||
video_webpage, count_name, default=None))
|
||||
|
||||
like_count = _extract_count('like')
|
||||
dislike_count = _extract_count('dislike')
|
||||
|
||||
# subtitles
|
||||
video_subtitles = self.extract_subtitles(video_id, video_webpage)
|
||||
automatic_captions = self.extract_automatic_captions(video_id, video_webpage)
|
||||
|
||||
video_duration = try_get(
|
||||
video_info, lambda x: int_or_none(x['length_seconds'][0]))
|
||||
if not video_duration:
|
||||
video_duration = parse_duration(self._html_search_meta(
|
||||
'duration', video_webpage, 'video duration'))
|
||||
|
||||
# annotations
|
||||
video_annotations = None
|
||||
if self._downloader.params.get('writeannotations', False):
|
||||
video_annotations = self._extract_annotations(video_id)
|
||||
|
||||
chapters = self._extract_chapters(description_original, video_duration)
|
||||
|
||||
def _extract_filesize(media_url):
|
||||
return int_or_none(self._search_regex(
|
||||
r'\bclen[=/](\d+)', media_url, 'filesize', default=None))
|
||||
@ -1990,6 +1875,133 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
raise ExtractorError(error_message, expected=True)
|
||||
raise ExtractorError('no conn, hlsvp or url_encoded_fmt_stream_map information found in video info')
|
||||
|
||||
# uploader
|
||||
video_uploader = try_get(video_info, lambda x: x['author'][0], compat_str)
|
||||
if video_uploader:
|
||||
video_uploader = compat_urllib_parse_unquote_plus(video_uploader)
|
||||
else:
|
||||
self._downloader.report_warning('unable to extract uploader name')
|
||||
|
||||
# uploader_id
|
||||
video_uploader_id = None
|
||||
video_uploader_url = None
|
||||
mobj = re.search(
|
||||
r'<link itemprop="url" href="(?P<uploader_url>https?://www\.youtube\.com/(?:user|channel)/(?P<uploader_id>[^"]+))">',
|
||||
video_webpage)
|
||||
if mobj is not None:
|
||||
video_uploader_id = mobj.group('uploader_id')
|
||||
video_uploader_url = mobj.group('uploader_url')
|
||||
else:
|
||||
self._downloader.report_warning('unable to extract uploader nickname')
|
||||
|
||||
# thumbnail image
|
||||
# We try first to get a high quality image:
|
||||
m_thumb = re.search(r'<span itemprop="thumbnail".*?href="(.*?)">',
|
||||
video_webpage, re.DOTALL)
|
||||
if m_thumb is not None:
|
||||
video_thumbnail = m_thumb.group(1)
|
||||
elif 'thumbnail_url' not in video_info:
|
||||
self._downloader.report_warning('unable to extract video thumbnail')
|
||||
video_thumbnail = None
|
||||
else: # don't panic if we can't find it
|
||||
video_thumbnail = compat_urllib_parse_unquote_plus(video_info['thumbnail_url'][0])
|
||||
|
||||
# upload date
|
||||
upload_date = self._html_search_meta(
|
||||
'datePublished', video_webpage, 'upload date', default=None)
|
||||
if not upload_date:
|
||||
upload_date = self._search_regex(
|
||||
[r'(?s)id="eow-date.*?>(.*?)</span>',
|
||||
r'(?:id="watch-uploader-info".*?>.*?|["\']simpleText["\']\s*:\s*["\'])(?:Published|Uploaded|Streamed live|Started) on (.+?)[<"\']'],
|
||||
video_webpage, 'upload date', default=None)
|
||||
upload_date = unified_strdate(upload_date)
|
||||
|
||||
video_license = self._html_search_regex(
|
||||
r'<h4[^>]+class="title"[^>]*>\s*License\s*</h4>\s*<ul[^>]*>\s*<li>(.+?)</li',
|
||||
video_webpage, 'license', default=None)
|
||||
|
||||
m_music = re.search(
|
||||
r'''(?x)
|
||||
<h4[^>]+class="title"[^>]*>\s*Music\s*</h4>\s*
|
||||
<ul[^>]*>\s*
|
||||
<li>(?P<title>.+?)
|
||||
by (?P<creator>.+?)
|
||||
(?:
|
||||
\(.+?\)|
|
||||
<a[^>]*
|
||||
(?:
|
||||
\bhref=["\']/red[^>]*>| # drop possible
|
||||
>\s*Listen ad-free with YouTube Red # YouTube Red ad
|
||||
)
|
||||
.*?
|
||||
)?</li
|
||||
''',
|
||||
video_webpage)
|
||||
if m_music:
|
||||
video_alt_title = remove_quotes(unescapeHTML(m_music.group('title')))
|
||||
video_creator = clean_html(m_music.group('creator'))
|
||||
else:
|
||||
video_alt_title = video_creator = None
|
||||
|
||||
def extract_meta(field):
|
||||
return self._html_search_regex(
|
||||
r'<h4[^>]+class="title"[^>]*>\s*%s\s*</h4>\s*<ul[^>]*>\s*<li>(.+?)</li>\s*' % field,
|
||||
video_webpage, field, default=None)
|
||||
|
||||
track = extract_meta('Song')
|
||||
artist = extract_meta('Artist')
|
||||
|
||||
m_episode = re.search(
|
||||
r'<div[^>]+id="watch7-headline"[^>]*>\s*<span[^>]*>.*?>(?P<series>[^<]+)</a></b>\s*S(?P<season>\d+)\s*•\s*E(?P<episode>\d+)</span>',
|
||||
video_webpage)
|
||||
if m_episode:
|
||||
series = m_episode.group('series')
|
||||
season_number = int(m_episode.group('season'))
|
||||
episode_number = int(m_episode.group('episode'))
|
||||
else:
|
||||
series = season_number = episode_number = None
|
||||
|
||||
m_cat_container = self._search_regex(
|
||||
r'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>',
|
||||
video_webpage, 'categories', default=None)
|
||||
if m_cat_container:
|
||||
category = self._html_search_regex(
|
||||
r'(?s)<a[^<]+>(.*?)</a>', m_cat_container, 'category',
|
||||
default=None)
|
||||
video_categories = None if category is None else [category]
|
||||
else:
|
||||
video_categories = None
|
||||
|
||||
video_tags = [
|
||||
unescapeHTML(m.group('content'))
|
||||
for m in re.finditer(self._meta_regex('og:video:tag'), video_webpage)]
|
||||
|
||||
def _extract_count(count_name):
|
||||
return str_to_int(self._search_regex(
|
||||
r'-%s-button[^>]+><span[^>]+class="yt-uix-button-content"[^>]*>([\d,]+)</span>'
|
||||
% re.escape(count_name),
|
||||
video_webpage, count_name, default=None))
|
||||
|
||||
like_count = _extract_count('like')
|
||||
dislike_count = _extract_count('dislike')
|
||||
|
||||
# subtitles
|
||||
video_subtitles = self.extract_subtitles(video_id, video_webpage)
|
||||
automatic_captions = self.extract_automatic_captions(video_id, video_webpage)
|
||||
|
||||
video_duration = try_get(
|
||||
video_info, lambda x: int_or_none(x['length_seconds'][0]))
|
||||
if not video_duration:
|
||||
video_duration = parse_duration(self._html_search_meta(
|
||||
'duration', video_webpage, 'video duration'))
|
||||
|
||||
# annotations
|
||||
video_annotations = None
|
||||
if self._downloader.params.get('writeannotations', False):
|
||||
video_annotations = self._extract_annotations(video_id)
|
||||
|
||||
chapters = self._extract_chapters(description_original, video_duration)
|
||||
|
||||
# Look for the DASH manifest
|
||||
if self._downloader.params.get('youtube_include_dash_manifest', True):
|
||||
dash_mpd_fatal = True
|
||||
@ -2055,9 +2067,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'uploader_url': video_uploader_url,
|
||||
'upload_date': upload_date,
|
||||
'license': video_license,
|
||||
'creator': video_creator,
|
||||
'creator': video_creator or artist,
|
||||
'title': video_title,
|
||||
'alt_title': video_alt_title,
|
||||
'alt_title': video_alt_title or track,
|
||||
'thumbnail': video_thumbnail,
|
||||
'description': video_description,
|
||||
'categories': video_categories,
|
||||
@ -2080,6 +2092,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'series': series,
|
||||
'season_number': season_number,
|
||||
'episode_number': episode_number,
|
||||
'track': track,
|
||||
'artist': artist,
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user