@@ -1652,8 +1652,63 @@ def extract_id(cls, url):
1652
1652
video_id = mobj .group (2 )
1653
1653
return video_id
1654
1654
1655
+ def _extract_chapters_from_json (self , webpage , video_id , duration ):
1656
+ if not webpage :
1657
+ return
1658
+ player = self ._parse_json (
1659
+ self ._search_regex (
1660
+ r'RELATED_PLAYER_ARGS["\']\s*:\s*({.+})\s*,?\s*\n' , webpage ,
1661
+ 'player args' , default = '{}' ),
1662
+ video_id , fatal = False )
1663
+ if not player or not isinstance (player , dict ):
1664
+ return
1665
+ watch_next_response = player .get ('watch_next_response' )
1666
+ if not isinstance (watch_next_response , compat_str ):
1667
+ return
1668
+ response = self ._parse_json (watch_next_response , video_id , fatal = False )
1669
+ if not response or not isinstance (response , dict ):
1670
+ return
1671
+ chapters_list = try_get (
1672
+ response ,
1673
+ lambda x : x ['playerOverlays' ]
1674
+ ['playerOverlayRenderer' ]
1675
+ ['decoratedPlayerBarRenderer' ]
1676
+ ['decoratedPlayerBarRenderer' ]
1677
+ ['playerBar' ]
1678
+ ['chapteredPlayerBarRenderer' ]
1679
+ ['chapters' ],
1680
+ list )
1681
+ if not chapters_list :
1682
+ return
1683
+
1684
+ def chapter_time (chapter ):
1685
+ return float_or_none (
1686
+ try_get (
1687
+ chapter ,
1688
+ lambda x : x ['chapterRenderer' ]['timeRangeStartMillis' ],
1689
+ int ),
1690
+ scale = 1000 )
1691
+ chapters = []
1692
+ for next_num , chapter in enumerate (chapters_list , start = 1 ):
1693
+ start_time = chapter_time (chapter )
1694
+ if start_time is None :
1695
+ continue
1696
+ end_time = (chapter_time (chapters_list [next_num ])
1697
+ if next_num < len (chapters_list ) else duration )
1698
+ if end_time is None :
1699
+ continue
1700
+ title = try_get (
1701
+ chapter , lambda x : x ['chapterRenderer' ]['title' ]['simpleText' ],
1702
+ compat_str )
1703
+ chapters .append ({
1704
+ 'start_time' : start_time ,
1705
+ 'end_time' : end_time ,
1706
+ 'title' : title ,
1707
+ })
1708
+ return chapters
1709
+
1655
1710
@staticmethod
1656
- def _extract_chapters (description , duration ):
1711
+ def _extract_chapters_from_description (description , duration ):
1657
1712
if not description :
1658
1713
return None
1659
1714
chapter_lines = re .findall (
@@ -1687,6 +1742,10 @@ def _extract_chapters(description, duration):
1687
1742
})
1688
1743
return chapters
1689
1744
1745
+ def _extract_chapters (self , webpage , description , video_id , duration ):
1746
+ return (self ._extract_chapters_from_json (webpage , video_id , duration )
1747
+ or self ._extract_chapters_from_description (description , duration ))
1748
+
1690
1749
def _real_extract (self , url ):
1691
1750
url , smuggled_data = unsmuggle_url (url , {})
1692
1751
@@ -2324,7 +2383,7 @@ def _extract_count(count_name):
2324
2383
errnote = 'Unable to download video annotations' , fatal = False ,
2325
2384
data = urlencode_postdata ({xsrf_field_name : xsrf_token }))
2326
2385
2327
- chapters = self ._extract_chapters (description_original , video_duration )
2386
+ chapters = self ._extract_chapters (video_webpage , description_original , video_id , video_duration )
2328
2387
2329
2388
# Look for the DASH manifest
2330
2389
if self ._downloader .params .get ('youtube_include_dash_manifest' , True ):
0 commit comments