|
@ -1652,8 +1652,63 @@ class YoutubeIE(YoutubeBaseInfoExtractor): |
|
|
video_id = mobj.group(2) |
|
|
video_id = mobj.group(2) |
|
|
return video_id |
|
|
return video_id |
|
|
|
|
|
|
|
|
|
|
|
def _extract_chapters_from_json(self, webpage, video_id, duration): |
|
|
|
|
|
if not webpage: |
|
|
|
|
|
return |
|
|
|
|
|
player = self._parse_json( |
|
|
|
|
|
self._search_regex( |
|
|
|
|
|
r'RELATED_PLAYER_ARGS["\']\s*:\s*({.+})\s*,?\s*\n', webpage, |
|
|
|
|
|
'player args', default='{}'), |
|
|
|
|
|
video_id, fatal=False) |
|
|
|
|
|
if not player or not isinstance(player, dict): |
|
|
|
|
|
return |
|
|
|
|
|
watch_next_response = player.get('watch_next_response') |
|
|
|
|
|
if not isinstance(watch_next_response, compat_str): |
|
|
|
|
|
return |
|
|
|
|
|
response = self._parse_json(watch_next_response, video_id, fatal=False) |
|
|
|
|
|
if not response or not isinstance(response, dict): |
|
|
|
|
|
return |
|
|
|
|
|
chapters_list = try_get( |
|
|
|
|
|
response, |
|
|
|
|
|
lambda x: x['playerOverlays'] |
|
|
|
|
|
['playerOverlayRenderer'] |
|
|
|
|
|
['decoratedPlayerBarRenderer'] |
|
|
|
|
|
['decoratedPlayerBarRenderer'] |
|
|
|
|
|
['playerBar'] |
|
|
|
|
|
['chapteredPlayerBarRenderer'] |
|
|
|
|
|
['chapters'], |
|
|
|
|
|
list) |
|
|
|
|
|
if not chapters_list: |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
def chapter_time(chapter): |
|
|
|
|
|
return float_or_none( |
|
|
|
|
|
try_get( |
|
|
|
|
|
chapter, |
|
|
|
|
|
lambda x: x['chapterRenderer']['timeRangeStartMillis'], |
|
|
|
|
|
int), |
|
|
|
|
|
scale=1000) |
|
|
|
|
|
chapters = [] |
|
|
|
|
|
for next_num, chapter in enumerate(chapters_list, start=1): |
|
|
|
|
|
start_time = chapter_time(chapter) |
|
|
|
|
|
if start_time is None: |
|
|
|
|
|
continue |
|
|
|
|
|
end_time = (chapter_time(chapters_list[next_num]) |
|
|
|
|
|
if next_num < len(chapters_list) else duration) |
|
|
|
|
|
if end_time is None: |
|
|
|
|
|
continue |
|
|
|
|
|
title = try_get( |
|
|
|
|
|
chapter, lambda x: x['chapterRenderer']['title']['simpleText'], |
|
|
|
|
|
compat_str) |
|
|
|
|
|
chapters.append({ |
|
|
|
|
|
'start_time': start_time, |
|
|
|
|
|
'end_time': end_time, |
|
|
|
|
|
'title': title, |
|
|
|
|
|
}) |
|
|
|
|
|
return chapters |
|
|
|
|
|
|
|
|
@staticmethod |
|
|
@staticmethod |
|
|
def _extract_chapters(description, duration): |
|
|
|
|
|
|
|
|
def _extract_chapters_from_description(description, duration): |
|
|
if not description: |
|
|
if not description: |
|
|
return None |
|
|
return None |
|
|
chapter_lines = re.findall( |
|
|
chapter_lines = re.findall( |
|
@ -1687,6 +1742,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): |
|
|
}) |
|
|
}) |
|
|
return chapters |
|
|
return chapters |
|
|
|
|
|
|
|
|
|
|
|
def _extract_chapters(self, webpage, description, video_id, duration): |
|
|
|
|
|
return (self._extract_chapters_from_json(webpage, video_id, duration) |
|
|
|
|
|
or self._extract_chapters_from_description(description, duration)) |
|
|
|
|
|
|
|
|
def _real_extract(self, url): |
|
|
def _real_extract(self, url): |
|
|
url, smuggled_data = unsmuggle_url(url, {}) |
|
|
url, smuggled_data = unsmuggle_url(url, {}) |
|
|
|
|
|
|
|
@ -2324,7 +2383,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): |
|
|
errnote='Unable to download video annotations', fatal=False, |
|
|
errnote='Unable to download video annotations', fatal=False, |
|
|
data=urlencode_postdata({xsrf_field_name: xsrf_token})) |
|
|
data=urlencode_postdata({xsrf_field_name: xsrf_token})) |
|
|
|
|
|
|
|
|
chapters = self._extract_chapters(description_original, video_duration) |
|
|
|
|
|
|
|
|
chapters = self._extract_chapters(video_webpage, description_original, video_id, video_duration) |
|
|
|
|
|
|
|
|
# Look for the DASH manifest |
|
|
# Look for the DASH manifest |
|
|
if self._downloader.params.get('youtube_include_dash_manifest', True): |
|
|
if self._downloader.params.get('youtube_include_dash_manifest', True): |
|
|