]>
Commit | Line | Data |
---|---|---|
76048b23 | 1 | from .common import InfoExtractor |
51d3045d S |
2 | from ..utils import ( |
3 | int_or_none, | |
4 | qualities, | |
7bf27721 | 5 | url_or_none, |
51d3045d | 6 | ) |
76048b23 | 7 | |
76048b23 | 8 | |
51d3045d | 9 | class NprIE(InfoExtractor): |
68867668 | 10 | _VALID_URL = r'https?://(?:www\.)?npr\.org/(?:sections/[^/]+/)?\d{4}/\d{2}/\d{2}/(?P<id>\d+)' |
51d3045d | 11 | _TESTS = [{ |
68867668 | 12 | 'url': 'https://www.npr.org/sections/allsongs/2015/10/21/449974205/new-music-from-beach-house-chairlift-cmj-discoveries-and-more', |
51d3045d S |
13 | 'info_dict': { |
14 | 'id': '449974205', | |
15 | 'title': 'New Music From Beach House, Chairlift, CMJ Discoveries And More' | |
16 | }, | |
17 | 'playlist_count': 7, | |
18 | }, { | |
68867668 | 19 | 'url': 'https://www.npr.org/sections/deceptivecadence/2015/10/09/446928052/music-from-the-shadows-ancient-armenian-hymns-and-piano-jazz', |
51d3045d S |
20 | 'info_dict': { |
21 | 'id': '446928052', | |
22 | 'title': "Songs We Love: Tigran Hamasyan, 'Your Mercy is Boundless'" | |
23 | }, | |
24 | 'playlist': [{ | |
25 | 'md5': '12fa60cb2d3ed932f53609d4aeceabf1', | |
26 | 'info_dict': { | |
27 | 'id': '446929930', | |
28 | 'ext': 'mp3', | |
29 | 'title': 'Your Mercy is Boundless (Bazum en Qo gtutyunqd)', | |
30 | 'duration': 402, | |
31 | }, | |
32 | }], | |
68867668 | 33 | }, { |
a0566bbf | 34 | # multimedia, not media title |
68867668 RA |
35 | 'url': 'https://www.npr.org/2017/06/19/533198237/tigers-jaw-tiny-desk-concert', |
36 | 'info_dict': { | |
37 | 'id': '533198237', | |
38 | 'title': 'Tigers Jaw: Tiny Desk Concert', | |
39 | }, | |
40 | 'playlist': [{ | |
41 | 'md5': '12fa60cb2d3ed932f53609d4aeceabf1', | |
42 | 'info_dict': { | |
43 | 'id': '533201718', | |
44 | 'ext': 'mp4', | |
45 | 'title': 'Tigers Jaw: Tiny Desk Concert', | |
46 | 'duration': 402, | |
47 | }, | |
48 | }], | |
49 | 'expected_warnings': ['Failed to download m3u8 information'], | |
7bf27721 S |
50 | }, { |
51 | # multimedia, no formats, stream | |
52 | 'url': 'https://www.npr.org/2020/02/14/805476846/laura-stevenson-tiny-desk-concert', | |
53 | 'only_matching': True, | |
e50c3500 | 54 | }, { |
55 | 'url': 'https://www.npr.org/2022/03/15/1084896560/bonobo-tiny-desk-home-concert', | |
56 | 'info_dict': { | |
57 | 'id': '1086468851', | |
58 | 'ext': 'mp4', | |
59 | 'title': 'Bonobo: Tiny Desk (Home) Concert', | |
60 | 'duration': 1061, | |
61 | 'thumbnail': r're:^https?://media.npr.org/assets/img/.*\.jpg$', | |
62 | }, | |
51d3045d | 63 | }] |
76048b23 | 64 | |
65 | def _real_extract(self, url): | |
51d3045d | 66 | playlist_id = self._match_id(url) |
76048b23 | 67 | |
68867668 RA |
68 | story = self._download_json( |
69 | 'http://api.npr.org/query', playlist_id, query={ | |
51d3045d | 70 | 'id': playlist_id, |
68867668 | 71 | 'fields': 'audio,multimedia,title', |
51d3045d S |
72 | 'format': 'json', |
73 | 'apiKey': 'MDAzMzQ2MjAyMDEyMzk4MTU1MDg3ZmM3MQ010', | |
68867668 RA |
74 | })['list']['story'][0] |
75 | playlist_title = story.get('title', {}).get('$text') | |
76048b23 | 76 | |
e50c3500 | 77 | # Fetch the JSON-LD from the npr page. |
78 | json_ld = self._search_json_ld( | |
79 | self._download_webpage(url, playlist_id), playlist_id, 'NewsArticle', fatal=False) | |
80 | ||
68867668 | 81 | KNOWN_FORMATS = ('threegp', 'm3u8', 'smil', 'mp4', 'mp3') |
51d3045d | 82 | quality = qualities(KNOWN_FORMATS) |
76048b23 | 83 | |
51d3045d | 84 | entries = [] |
68867668 RA |
85 | for media in story.get('audio', []) + story.get('multimedia', []): |
86 | media_id = media['id'] | |
87 | ||
51d3045d | 88 | formats = [] |
68867668 | 89 | for format_id, formats_entry in media.get('format', {}).items(): |
51d3045d S |
90 | if not formats_entry: |
91 | continue | |
92 | if isinstance(formats_entry, list): | |
93 | formats_entry = formats_entry[0] | |
94 | format_url = formats_entry.get('$text') | |
95 | if not format_url: | |
96 | continue | |
97 | if format_id in KNOWN_FORMATS: | |
68867668 RA |
98 | if format_id == 'm3u8': |
99 | formats.extend(self._extract_m3u8_formats( | |
100 | format_url, media_id, 'mp4', 'm3u8_native', | |
101 | m3u8_id='hls', fatal=False)) | |
102 | elif format_id == 'smil': | |
103 | smil_formats = self._extract_smil_formats( | |
104 | format_url, media_id, transform_source=lambda s: s.replace( | |
774a46c5 | 105 | 'rtmp://flash.npr.org/ondemand/', 'https://ondemand.npr.org/'), |
106 | fatal=False) | |
68867668 RA |
107 | self._check_formats(smil_formats, media_id) |
108 | formats.extend(smil_formats) | |
109 | else: | |
110 | formats.append({ | |
111 | 'url': format_url, | |
112 | 'format_id': format_id, | |
113 | 'quality': quality(format_id), | |
114 | }) | |
7bf27721 S |
115 | for stream_id, stream_entry in media.get('stream', {}).items(): |
116 | if not isinstance(stream_entry, dict): | |
117 | continue | |
118 | if stream_id != 'hlsUrl': | |
119 | continue | |
120 | stream_url = url_or_none(stream_entry.get('$text')) | |
121 | if not stream_url: | |
122 | continue | |
123 | formats.extend(self._extract_m3u8_formats( | |
124 | stream_url, stream_id, 'mp4', 'm3u8_native', | |
125 | m3u8_id='hls', fatal=False)) | |
e50c3500 | 126 | |
127 | if not formats and json_ld.get('url'): | |
128 | formats.extend(self._extract_m3u8_formats(json_ld['url'], media_id, 'mp4', m3u8_id='hls', fatal=False)) | |
129 | ||
51d3045d | 130 | self._sort_formats(formats) |
68867668 | 131 | |
51d3045d | 132 | entries.append({ |
68867668 RA |
133 | 'id': media_id, |
134 | 'title': media.get('title', {}).get('$text') or playlist_title, | |
135 | 'thumbnail': media.get('altImageUrl', {}).get('$text'), | |
136 | 'duration': int_or_none(media.get('duration', {}).get('$text')), | |
51d3045d S |
137 | 'formats': formats, |
138 | }) | |
76048b23 | 139 | |
51d3045d | 140 | return self.playlist_result(entries, playlist_id, playlist_title) |