[stitcher] fix extraction(closes #20811)(closes #27606)
authorRemita Amine <remitamine@gmail.com>
Fri, 1 Jan 2021 17:30:56 +0000 (18:30 +0100)
committerRemita Amine <remitamine@gmail.com>
Fri, 1 Jan 2021 17:38:46 +0000 (18:38 +0100)
youtube_dl/extractor/stitcher.py

index 97d1ff6811b27140c77932a766b7cb9d3dbfe7b6..b8b5711b1b2ca43576cfdbc361e52201c9d11f28 100644 (file)
@@ -4,25 +4,28 @@ import re
 
 from .common import InfoExtractor
 from ..utils import (
-    determine_ext,
+    clean_html,
+    ExtractorError,
     int_or_none,
-    js_to_json,
-    unescapeHTML,
+    str_or_none,
+    try_get,
 )
 
 
 class StitcherIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?stitcher\.com/podcast/(?:[^/]+/)+e/(?:(?P<display_id>[^/#?&]+?)-)?(?P<id>\d+)(?:[/#?&]|$)'
+    _VALID_URL = r'https?://(?:www\.)?stitcher\.com/(?:podcast|show)/(?:[^/]+/)+e(?:pisode)?/(?:(?P<display_id>[^/#?&]+?)-)?(?P<id>\d+)(?:[/#?&]|$)'
     _TESTS = [{
         'url': 'http://www.stitcher.com/podcast/the-talking-machines/e/40789481?autoplay=true',
-        'md5': '391dd4e021e6edeb7b8e68fbf2e9e940',
+        'md5': 'e9635098e0da10b21a0e2b85585530f6',
         'info_dict': {
             'id': '40789481',
             'ext': 'mp3',
             'title': 'Machine Learning Mastery and Cancer Clusters',
-            'description': 'md5:55163197a44e915a14a1ac3a1de0f2d3',
+            'description': 'md5:547adb4081864be114ae3831b4c2b42f',
             'duration': 1604,
             'thumbnail': r're:^https?://.*\.jpg',
+            'upload_date': '20180126',
+            'timestamp': 1516989316,
         },
     }, {
         'url': 'http://www.stitcher.com/podcast/panoply/vulture-tv/e/the-rare-hourlong-comedy-plus-40846275?autoplay=true',
@@ -38,6 +41,7 @@ class StitcherIE(InfoExtractor):
         'params': {
             'skip_download': True,
         },
+        'skip': 'Page Not Found',
     }, {
         # escaped title
         'url': 'http://www.stitcher.com/podcast/marketplace-on-stitcher/e/40910226?autoplay=true',
@@ -45,37 +49,39 @@ class StitcherIE(InfoExtractor):
     }, {
         'url': 'http://www.stitcher.com/podcast/panoply/getting-in/e/episode-2a-how-many-extracurriculars-should-i-have-40876278?autoplay=true',
         'only_matching': True,
+    }, {
+        'url': 'https://www.stitcher.com/show/threedom/episode/circles-on-a-stick-200212584',
+        'only_matching': True,
     }]
 
     def _real_extract(self, url):
-        mobj = re.match(self._VALID_URL, url)
-        audio_id = mobj.group('id')
-        display_id = mobj.group('display_id') or audio_id
+        display_id, audio_id = re.match(self._VALID_URL, url).groups()
 
-        webpage = self._download_webpage(url, display_id)
+        resp = self._download_json(
+            'https://api.prod.stitcher.com/episode/' + audio_id,
+            display_id or audio_id)
+        episode = try_get(resp, lambda x: x['data']['episodes'][0], dict)
+        if not episode:
+            raise ExtractorError(resp['errors'][0]['message'], expected=True)
 
-        episode = self._parse_json(
-            js_to_json(self._search_regex(
-                r'(?s)var\s+stitcher(?:Config)?\s*=\s*({.+?});\n', webpage, 'episode config')),
-            display_id)['config']['episode']
+        title = episode['title'].strip()
+        audio_url = episode['audio_url']
 
-        title = unescapeHTML(episode['title'])
-        formats = [{
-            'url': episode[episode_key],
-            'ext': determine_ext(episode[episode_key]) or 'mp3',
-            'vcodec': 'none',
-        } for episode_key in ('episodeURL',) if episode.get(episode_key)]
-        description = self._search_regex(
-            r'Episode Info:\s*</span>([^<]+)<', webpage, 'description', fatal=False)
-        duration = int_or_none(episode.get('duration'))
-        thumbnail = episode.get('episodeImage')
+        thumbnail = None
+        show_id = episode.get('show_id')
+        if show_id and episode.get('classic_id') != -1:
+            thumbnail = 'https://stitcher-classic.imgix.net/feedimages/%s.jpg' % show_id
 
         return {
             'id': audio_id,
             'display_id': display_id,
             'title': title,
-            'description': description,
-            'duration': duration,
+            'description': clean_html(episode.get('html_description') or episode.get('description')),
+            'duration': int_or_none(episode.get('duration')),
             'thumbnail': thumbnail,
-            'formats': formats,
+            'url': audio_url,
+            'vcodec': 'none',
+            'timestamp': int_or_none(episode.get('date_created')),
+            'season_number': int_or_none(episode.get('season')),
+            'season_id': str_or_none(episode.get('season_id')),
         }