3 from .common
import InfoExtractor
20 class NhkBaseIE(InfoExtractor
):
21 _API_URL_TEMPLATE
= 'https://nwapi.nhk.jp/nhkworld/%sod%slist/v7b/%s/%s/%s/all%s.json'
22 _BASE_URL_REGEX
= r
'https?://www3\.nhk\.or\.jp/nhkworld/(?P<lang>[a-z]{2})/ondemand'
23 _TYPE_REGEX
= r
'/(?P<type>video|audio)/'
25 def _call_api(self
, m_id
, lang
, is_video
, is_episode
, is_clip
):
26 return self
._download
_json
(
27 self
._API
_URL
_TEMPLATE
% (
28 'v' if is_video
else 'r',
29 'clip' if is_clip
else 'esd',
30 'episode' if is_episode
else 'program',
31 m_id
, lang
, '/all' if is_video
else ''),
32 m_id
, query
={'apikey': 'EJfK8jdS57GqlupFgAfAAwr573q01y6k'}
)['data']['episodes'] or []
34 def _get_api_info(self
, refresh
=True):
36 return self
.cache
.load('nhk', 'api_info')
38 self
.cache
.store('nhk', 'api_info', {})
39 movie_player_js
= self
._download
_webpage
(
40 'https://movie-a.nhk.or.jp/world/player/js/movie-player.js', None,
41 note
='Downloading stream API information')
43 'url': self
._search
_regex
(
44 r
'prod:[^;]+\bapiUrl:\s*[\'"]([^\'"]+)[\'"]', movie_player_js, None, 'stream API url'),
45 'token': self._search_regex(
46 r'prod:[^;]+\btoken:\s*[\'"]([^
\'"]+)[\'"]', movie_player_js, None, 'stream API token
'),
48 self.cache.store('nhk
', 'api_info
', api_info)
51 def _extract_stream_info(self, vod_id):
52 for refresh in (False, True):
53 api_info = self._get_api_info(refresh)
57 api_url = api_info.pop('url
')
60 api_url, vod_id, 'Downloading stream url info
', fatal=False, query={
63 'optional_id
': vod_id,
66 stream_url = traverse_obj(
67 meta, ('movie_url
', ('mb_auto
', 'auto_sp
', 'auto_pc
'), {url_or_none}), get_all=False)
70 formats, subtitles = self._extract_m3u8_formats_and_subtitles(stream_url, vod_id)
72 **traverse_obj(meta, {
73 'duration
': ('duration
', {int_or_none}),
74 'timestamp
': ('publication_date
', {unified_timestamp}),
75 'release_timestamp
': ('insert_date
', {unified_timestamp}),
76 'modified_timestamp
': ('update_date
', {unified_timestamp}),
79 'subtitles
': subtitles,
81 raise ExtractorError('Unable to extract stream url
')
83 def _extract_episode_info(self, url, episode=None):
84 fetch_episode = episode is None
85 lang, m_type, episode_id = NhkVodIE._match_valid_url(url).group('lang
', 'type', 'id')
86 is_video = m_type == 'video
'
89 episode_id = episode_id[:4] + '-' + episode_id[4:]
92 episode = self._call_api(
93 episode_id, lang, is_video, True, episode_id[:4] == '9999')[0]
95 def get_clean_field(key):
96 return clean_html(episode.get(key + '_clean
') or episode.get(key))
98 title = get_clean_field('sub_title
')
99 series = get_clean_field('title
')
102 for s, w, h in [('', 640, 360), ('_l
', 1280, 720)]:
103 img_path = episode.get('image
' + s)
110 'url
': 'https
://www3
.nhk
.or.jp
' + img_path,
115 title = f'{series}
- {title}
'
116 elif series and not title:
120 else: # title, no series
124 'id': episode_id + '-' + lang,
126 'description
': get_clean_field('description
'),
127 'thumbnails
': thumbnails,
129 'episode
': episode_name,
133 vod_id = episode['vod_id
']
135 **self._extract_stream_info(vod_id),
141 audio_path = episode['audio
']['audio
']
142 info['formats
'] = self._extract_m3u8_formats(
143 'https
://nhkworld
-vh
.akamaihd
.net
/i
%s/master
.m3u8
' % audio_path,
144 episode_id, 'm4a
', entry_protocol='m3u8_native
',
145 m3u8_id='hls
', fatal=False)
146 for f in info['formats
']:
150 '_type
': 'url_transparent
',
151 'ie_key
': NhkVodIE.ie_key(),
157 class NhkVodIE(NhkBaseIE):
158 # the 7-character IDs can have alphabetic chars too: assume [a-z] rather than just [a-f], eg
159 _VALID_URL = [rf'{NhkBaseIE._BASE_URL_REGEX}
/(?P
<type>video
)/(?P
<id>[0-9a
-z
]+)',
160 rf'{NhkBaseIE._BASE_URL_REGEX}
/(?P
<type>audio
)/(?P
<id>[^
/?
#]+?-\d{{8}}-[0-9a-z]+)']
161 # Content available only for a limited period of time. Visit
162 # https://www3.nhk.or.jp/nhkworld/en/ondemand/ for working samples.
164 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2049126/',
166 'id': 'nw_vod_v_en_2049_126_20230413233000_01_1681398302',
168 'title': 'Japan Railway Journal - The Tohoku Shinkansen: Full Speed Ahead',
169 'description': 'md5:49f7c5b206e03868a2fdf0d0814b92f6',
170 'thumbnail': 'md5:51bcef4a21936e7fea1ff4e06353f463',
171 'episode': 'The Tohoku Shinkansen: Full Speed Ahead',
172 'series': 'Japan Railway Journal',
173 'modified_timestamp': 1694243656,
174 'timestamp': 1681428600,
175 'release_timestamp': 1693883728,
177 'upload_date': '20230413',
178 'modified_date': '20230909',
179 'release_date': '20230905',
184 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999011/',
185 'md5': '153c3016dfd252ba09726588149cf0e7',
187 'id': 'lpZXIwaDE6_Z-976CPsFdxyICyWUzlT5',
189 'title': 'Dining with the Chef - Chef Saito\'s Family recipe: MENCHI-KATSU',
190 'description': 'md5:5aee4a9f9d81c26281862382103b0ea5',
191 'thumbnail': 'md5:d6a4d9b6e9be90aaadda0bcce89631ed',
192 'series': 'Dining with the Chef',
193 'episode': 'Chef Saito\'s Family recipe: MENCHI-KATSU',
195 'upload_date': '20190816',
196 'release_date': '20230902',
197 'release_timestamp': 1693619292,
198 'modified_timestamp': 1694168033,
199 'modified_date': '20230908',
200 'timestamp': 1565997540,
204 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/audio/livinginjapan-20231001-1/',
206 'id': 'livinginjapan-20231001-1-en',
208 'title': 'Living in Japan - Tips for Travelers to Japan / Ramen Vending Machines',
209 'series': 'Living in Japan',
210 'description': 'md5:0a0e2077d8f07a03071e990a6f51bfab',
211 'thumbnail': 'md5:960622fb6e06054a4a1a0c97ea752545',
212 'episode': 'Tips for Travelers to Japan / Ramen Vending Machines'
215 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2015173/',
216 'only_matching': True,
218 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/audio/plugin-20190404-1/',
219 'only_matching': True,
221 'url': 'https://www3.nhk.or.jp/nhkworld/fr/ondemand/audio/plugin-20190404-1/',
222 'only_matching': True,
224 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/audio/j_art-20150903-1/',
225 'only_matching': True,
227 # video, alphabetic character in ID #29670
228 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999a34/',
232 'title': 'DESIGN TALKS plus - Fishermen’s Finery',
233 'description': 'md5:8a8f958aaafb0d7cb59d38de53f1e448',
234 'thumbnail': r
're:^https?:/(/[a-z0-9.-]+)+\.jpg\?w=1920&h=1080$',
235 'upload_date': '20210615',
236 'timestamp': 1623722008,
238 'skip': '404 Not Found',
240 # japanese-language, longer id than english
241 'url': 'https://www3.nhk.or.jp/nhkworld/ja/ondemand/video/0020271111/',
243 'id': 'nw_ja_v_jvod_ohayou_20231008',
245 'title': 'おはよう日本(7時台) - 10月8日放送',
246 'series': 'おはよう日本(7時台)',
247 'episode': '10月8日放送',
248 'thumbnail': 'md5:d733b1c8e965ab68fb02b2d347d0e9b4',
249 'description': 'md5:9c1d6cbeadb827b955b20e99ab920ff0',
251 'skip': 'expires 2023-10-15',
253 # a one-off (single-episode series). title from the api is just '<p></p>'
254 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/3004952/',
256 'id': 'nw_vod_v_en_3004_952_20230723091000_01_1690074552',
258 'title': 'Barakan Discovers AMAMI OSHIMA: Isson\'s Treasure Island',
259 'description': 'md5:5db620c46a0698451cc59add8816b797',
260 'thumbnail': 'md5:67d9ff28009ba379bfa85ad1aaa0e2bd',
261 'release_date': '20230905',
262 'timestamp': 1690103400,
264 'release_timestamp': 1693898699,
265 'modified_timestamp': 1698057495,
266 'modified_date': '20231023',
267 'upload_date': '20230723',
271 def _real_extract(self
, url
):
272 return self
._extract
_episode
_info
(url
)
275 class NhkVodProgramIE(NhkBaseIE
):
276 _VALID_URL
= rf
'{NhkBaseIE._BASE_URL_REGEX}/program{NhkBaseIE._TYPE_REGEX}(?P<id>\w+)(?:.+?\btype=(?P<episode_type>clip|(?:radio|tv)Episode))?'
278 # video program episodes
279 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/sumo',
282 'title': 'GRAND SUMO Highlights',
283 'description': 'md5:fc20d02dc6ce85e4b72e0273aa52fdbf',
285 'playlist_mincount': 0,
287 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/japanrailway',
289 'id': 'japanrailway',
290 'title': 'Japan Railway Journal',
291 'description': 'md5:ea39d93af7d05835baadf10d1aae0e3f',
293 'playlist_mincount': 12,
295 # video program clips
296 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/japanrailway/?type=clip',
298 'id': 'japanrailway',
299 'title': 'Japan Railway Journal',
300 'description': 'md5:ea39d93af7d05835baadf10d1aae0e3f',
302 'playlist_mincount': 5,
304 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/10yearshayaomiyazaki/',
305 'only_matching': True,
308 'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/audio/listener/',
309 'only_matching': True,
312 def _real_extract(self
, url
):
313 lang
, m_type
, program_id
, episode_type
= self
._match
_valid
_url
(url
).group('lang', 'type', 'id', 'episode_type')
314 episodes
= self
._call
_api
(
315 program_id
, lang
, m_type
== 'video', False, episode_type
== 'clip')
318 for episode
in episodes
:
319 episode_path
= episode
.get('url')
322 entries
.append(self
._extract
_episode
_info
(
323 urljoin(url
, episode_path
), episode
))
325 html
= self
._download
_webpage
(url
, program_id
)
326 program_title
= clean_html(get_element_by_class('p-programDetail__title', html
))
327 program_description
= clean_html(get_element_by_class('p-programDetail__text', html
))
329 return self
.playlist_result(entries
, program_id
, program_title
, program_description
)
332 class NhkForSchoolBangumiIE(InfoExtractor
):
333 _VALID_URL
= r
'https?://www2\.nhk\.or\.jp/school/movie/(?P<type>bangumi|clip)\.cgi\?das_id=(?P<id>[a-zA-Z0-9_-]+)'
335 'url': 'https://www2.nhk.or.jp/school/movie/bangumi.cgi?das_id=D0005150191_00000',
337 'id': 'D0005150191_00003',
340 'timestamp': 1396414800,
342 'upload_date': '20140402',
345 'chapters': 'count:12'
349 'skip_download': True,
353 def _real_extract(self
, url
):
354 program_type
, video_id
= self
._match
_valid
_url
(url
).groups()
356 webpage
= self
._download
_webpage
(
357 f
'https://www2.nhk.or.jp/school/movie/{program_type}.cgi?das_id={video_id}', video_id
)
359 # searches all variables
360 base_values
= {g.group(1): g.group(2) for g in re.finditer(r'var\s+([a-zA-Z_]+)\s*=\s*"([^"]+?)";', webpage)}
361 # and programObj values too
362 program_values
= {g.group(1): g.group(3) for g in re.finditer(r'(?:program|clip)Obj\.([a-zA-Z_]+)\s*=\s*(["\'])([^"]+?)\2;', webpage)}
363 # extract all chapters
364 chapter_durations
= [parse_duration(g
.group(1)) for g
in re
.finditer(r
'chapterTime\.push\(\'([0-9:]+?
)\'\
);', webpage)]
365 chapter_titles = [' '.join([g.group(1) or '', unescapeHTML(g.group(2))]).strip() for g in re.finditer(r'<div
class="cpTitle"><span
>(scene\s
*\d
+)?
</span
>([^
<]+?
)</div
>', webpage)]
367 # this is how player_core.js is actually doing (!)
368 version = base_values.get('r_version
') or program_values.get('version
')
370 video_id = f'{video_id.split("_")[0]}_{version}
'
372 formats = self._extract_m3u8_formats(
373 f'https
://nhks
-vh
.akamaihd
.net
/i
/das
/{video_id[0:8]}
/{video_id}_V_000
.f4v
/master
.m3u8
',
374 video_id, ext='mp4
', m3u8_id='hls
')
376 duration = parse_duration(base_values.get('r_duration
'))
379 if chapter_durations and chapter_titles and len(chapter_durations) == len(chapter_titles):
380 start_time = chapter_durations
381 end_time = chapter_durations[1:] + [duration]
386 } for s, e, t in zip(start_time, end_time, chapter_titles)]
390 'title
': program_values.get('name
'),
391 'duration
': parse_duration(base_values.get('r_duration
')),
392 'timestamp
': unified_timestamp(base_values['r_upload
']),
394 'chapters
': chapters,
398 class NhkForSchoolSubjectIE(InfoExtractor):
399 IE_DESC = 'Portal page
for each school subjects
, like
Japanese (kokugo
, 国語
) or math (sansuu
/suugaku
or 算数・数学
)'
401 'rika
', 'syakai
', 'kokugo
',
402 'sansuu
', 'seikatsu
', 'doutoku
',
403 'ongaku
', 'taiiku
', 'zukou
',
404 'gijutsu
', 'katei
', 'sougou
',
408 _VALID_URL = r'https?
://www\
.nhk\
.or\
.jp
/school
/(?P
<id>%s)/?
(?
:[\?#].*)?$' % '|'.join(re.escape(s) for s in KNOWN_SUBJECTS)
411 'url': 'https://www.nhk.or.jp/school/sougou/',
414 'title': '総合的な学習の時間',
416 'playlist_mincount': 16,
418 'url': 'https://www.nhk.or.jp/school/rika/',
423 'playlist_mincount': 15,
426 def _real_extract(self
, url
):
427 subject_id
= self
._match
_id
(url
)
428 webpage
= self
._download
_webpage
(url
, subject_id
)
430 return self
.playlist_from_matches(
431 re
.finditer(rf
'href="((?:https?://www\.nhk\.or\.jp)?/school/{re.escape(subject_id)}/[^/]+/)"', webpage
),
433 self
._html
_search
_regex
(r
'(?s)<span\s+class="subjectName">\s*<img\s*[^<]+>\s*([^<]+?)</span>', webpage
, 'title', fatal
=False),
434 lambda g
: urljoin(url
, g
.group(1)))
437 class NhkForSchoolProgramListIE(InfoExtractor
):
438 _VALID_URL
= r
'https?://www\.nhk\.or\.jp/school/(?P<id>(?:%s)/[a-zA-Z0-9_-]+)' % (
439 '|'.join(re
.escape(s
) for s
in NhkForSchoolSubjectIE
.KNOWN_SUBJECTS
)
442 'url': 'https://www.nhk.or.jp/school/sougou/q/',
445 'title': 'Q~こどものための哲学',
447 'playlist_mincount': 20,
450 def _real_extract(self
, url
):
451 program_id
= self
._match
_id
(url
)
453 webpage
= self
._download
_webpage
(f
'https://www.nhk.or.jp/school/{program_id}/', program_id
)
455 title
= (self
._generic
_title
('', webpage
)
456 or self
._html
_search
_regex
(r
'<h3>([^<]+?)とは?\s*</h3>', webpage
, 'title', fatal
=False))
457 title
= re
.sub(r
'\s*\|\s*NHK\s+for\s+School\s*$', '', title
) if title
else None
458 description
= self
._html
_search
_regex
(
459 r
'(?s)<div\s+class="programDetail\s*">\s*<p>[^<]+</p>',
460 webpage
, 'description', fatal
=False, group
=0)
462 bangumi_list
= self
._download
_json
(
463 f
'https://www.nhk.or.jp/school/{program_id}/meta/program.json', program_id
)
464 # they're always bangumi
466 self
.url_result(f
'https://www2.nhk.or.jp/school/movie/bangumi.cgi?das_id={x}')
467 for x
in traverse_obj(bangumi_list
, ('part', ..., 'part-video-dasid')) or []]
469 return self
.playlist_result(bangumis
, program_id
, title
, description
)
472 class NhkRadiruIE(InfoExtractor
):
473 _GEO_COUNTRIES
= ['JP']
474 IE_DESC
= 'NHK らじる (Radiru/Rajiru)'
475 _VALID_URL
= r
'https?://www\.nhk\.or\.jp/radio/(?:player/ondemand|ondemand/detail)\.html\?p=(?P<site>[\da-zA-Z]+)_(?P<corner>[\da-zA-Z]+)(?:_(?P<headline>[\da-zA-Z]+))?'
477 'url': 'https://www.nhk.or.jp/radio/player/ondemand.html?p=0449_01_3926210',
478 'skip': 'Episode expired on 2024-02-24',
480 'title': 'ジャズ・トゥナイト シリーズJAZZジャイアンツ 56 ジョニー・ホッジス',
481 'id': '0449_01_3926210',
483 'series': 'ジャズ・トゥナイト',
484 'uploader': 'NHK-FM',
486 'thumbnail': 'https://www.nhk.or.jp/prog/img/449/g449.jpg',
487 'release_date': '20240217',
488 'description': 'md5:a456ee8e5e59e6dd2a7d32e62386e811',
489 'timestamp': 1708185600,
490 'release_timestamp': 1708178400,
491 'upload_date': '20240217',
494 # playlist, airs every weekday so it should _hopefully_ be okay forever
495 'url': 'https://www.nhk.or.jp/radio/ondemand/detail.html?p=0458_01',
498 'title': 'ベストオブクラシック',
499 'description': '世界中の上質な演奏会をじっくり堪能する本格派クラシック番組。',
501 'uploader': 'NHK-FM',
502 'thumbnail': 'https://www.nhk.or.jp/prog/img/458/g458.jpg',
504 'playlist_mincount': 3,
506 # one with letters in the id
507 'url': 'https://www.nhk.or.jp/radio/player/ondemand.html?p=F300_06_3738470',
508 'note': 'Expires on 2024-03-31',
510 'id': 'F300_06_3738470',
512 'title': '有島武郎「一房のぶどう」',
513 'description': '朗読:川野一宇(ラジオ深夜便アンカー)\r\n\r\n(2016年12月8日放送「ラジオ深夜便『アンカー朗読シリーズ』」より)',
514 'channel': 'NHKラジオ第1、NHK-FM',
515 'uploader': 'NHKラジオ第1、NHK-FM',
516 'timestamp': 1635757200,
517 'thumbnail': 'https://www.nhk.or.jp/radioondemand/json/F300/img/corner/box_109_thumbnail.jpg',
518 'release_date': '20161207',
519 'series': 'らじる文庫 by ラジオ深夜便 ',
520 'release_timestamp': 1481126700,
521 'upload_date': '20211101',
523 'expected_warnings': ['Unable to download JSON metadata', 'Failed to get extended description'],
526 'url': 'https://www.nhk.or.jp/radio/player/ondemand.html?p=F261_01_3855109',
527 'skip': 'Expires on 2023-04-17',
529 'id': 'F261_01_3855109',
531 'channel': 'NHKラジオ第1',
532 'uploader': 'NHKラジオ第1',
533 'timestamp': 1681635900,
534 'release_date': '20230416',
535 'series': 'NHKラジオニュース',
536 'title': '午後6時のNHKニュース',
537 'thumbnail': 'https://www.nhk.or.jp/radioondemand/json/F261/img/RADIONEWS_640.jpg',
538 'upload_date': '20230416',
539 'release_timestamp': 1681635600,
545 def _extract_extended_description(self
, episode_id
, episode
):
546 service
, _
, area
= traverse_obj(episode
, ('aa_vinfo2', {str}
, {lambda x: (x or '').partition(',')}
))
547 aa_vinfo3
= traverse_obj(episode
, ('aa_vinfo3', {str}
))
548 detail_url
= try_call(
549 lambda: self
._API
_URL
_TMPL
.format(service
=service
, area
=area
, dateid
=aa_vinfo3
))
553 full_meta
= traverse_obj(
554 self
._download
_json
(detail_url
, episode_id
, 'Downloading extended metadata', fatal
=False),
555 ('list', service
, 0, {dict}
)) or {}
556 return join_nonempty('subtitle', 'content', 'act', 'music', delim
='\n\n', from_dict
=full_meta
)
558 def _extract_episode_info(self
, headline
, programme_id
, series_meta
):
559 episode_id
= f
'{programme_id}_{headline["headline_id"]}'
560 episode
= traverse_obj(headline
, ('file_list', 0, {dict}
))
561 description
= self
._extract
_extended
_description
(episode_id
, episode
)
563 self
.report_warning('Failed to get extended description, falling back to summary')
564 description
= traverse_obj(episode
, ('file_title_sub', {str}
))
569 'formats': self
._extract
_m
3u8_formats
(episode
.get('file_name'), episode_id
, fatal
=False),
570 'container': 'm4a_dash', # force fixup, AAC-only HLS
572 'series': series_meta
.get('title'),
573 'thumbnail': url_or_none(headline
.get('headline_image')) or series_meta
.get('thumbnail'),
574 'description': description
,
575 **traverse_obj(episode
, {
576 'title': 'file_title',
577 'timestamp': ('open_time', {unified_timestamp}
),
578 'release_timestamp': ('aa_vinfo4', {lambda x: x.split('_')[0]}
, {unified_timestamp}
),
582 def _real_initialize(self
):
583 if self
._API
_URL
_TMPL
:
585 api_config
= self
._download
_xml
(
586 'https://www.nhk.or.jp/radio/config/config_web.xml', None, 'Downloading API config', fatal
=False)
587 NhkRadiruIE
._API
_URL
_TMPL
= try_call(lambda: f
'https:{api_config.find(".//url_program_detail").text}')
589 def _real_extract(self
, url
):
590 site_id
, corner_id
, headline_id
= self
._match
_valid
_url
(url
).group('site', 'corner', 'headline')
591 programme_id
= f
'{site_id}_{corner_id}'
593 if site_id
== 'F261':
594 json_url
= 'https://www.nhk.or.jp/s-media/news/news-site/list/v1/all.json'
596 json_url
= f
'https://www.nhk.or.jp/radioondemand/json/{site_id}/bangumi_{programme_id}.json'
598 meta
= self
._download
_json
(json_url
, programme_id
)['main']
600 series_meta
= traverse_obj(meta
, {
601 'title': 'program_name',
602 'channel': 'media_name',
603 'uploader': 'media_name',
604 'thumbnail': (('thumbnail_c', 'thumbnail_p'), {url_or_none}
),
608 return self
._extract
_episode
_info
(
610 'detail_list', lambda _
, v
: v
['headline_id'] == headline_id
), get_all
=False),
611 programme_id
, series_meta
)
614 for headline
in traverse_obj(meta
, ('detail_list', ..., {dict}
)):
615 yield self
._extract
_episode
_info
(headline
, programme_id
, series_meta
)
617 return self
.playlist_result(
618 entries(), programme_id
, playlist_description
=meta
.get('site_detail'), **series_meta
)
621 class NhkRadioNewsPageIE(InfoExtractor
):
622 _VALID_URL
= r
'https?://www\.nhk\.or\.jp/radionews/?(?:$|[?#])'
624 # airs daily, on-the-hour most hours
625 'url': 'https://www.nhk.or.jp/radionews/',
626 'playlist_mincount': 5,
629 'thumbnail': 'https://www.nhk.or.jp/radioondemand/json/F261/img/RADIONEWS_640.jpg',
630 'description': 'md5:bf2c5b397e44bc7eb26de98d8f15d79d',
631 'channel': 'NHKラジオ第1',
632 'uploader': 'NHKラジオ第1',
633 'title': 'NHKラジオニュース',
637 def _real_extract(self
, url
):
638 return self
.url_result('https://www.nhk.or.jp/radio/ondemand/detail.html?p=F261_01', NhkRadiruIE
)
641 class NhkRadiruLiveIE(InfoExtractor
):
642 _GEO_COUNTRIES
= ['JP']
643 _VALID_URL
= r
'https?://www\.nhk\.or\.jp/radio/player/\?ch=(?P<id>r[12]|fm)'
645 # radio 1, no area specified
646 'url': 'https://www.nhk.or.jp/radio/player/?ch=r1',
649 'title': 're:^NHKネットラジオ第1 東京.+$',
651 'thumbnail': 'https://www.nhk.or.jp/common/img/media/r1-200x200.png',
652 'live_status': 'is_live',
655 # radio 2, area specified
656 # (the area doesnt actually matter, r2 is national)
657 'url': 'https://www.nhk.or.jp/radio/player/?ch=r2',
658 'params': {'extractor_args': {'nhkradirulive': {'area': ['fukuoka']}
}},
661 'title': 're:^NHKネットラジオ第2 福岡.+$',
663 'thumbnail': 'https://www.nhk.or.jp/common/img/media/r2-200x200.png',
664 'live_status': 'is_live',
668 'url': 'https://www.nhk.or.jp/radio/player/?ch=fm',
669 'params': {'extractor_args': {'nhkradirulive': {'area': ['sapporo']}
}},
672 'title': 're:^NHKネットラジオFM 札幌.+$',
674 'thumbnail': 'https://www.nhk.or.jp/common/img/media/fm-200x200.png',
675 'live_status': 'is_live',
679 _NOA_STATION_IDS
= {'r1': 'n1', 'r2': 'n2', 'fm': 'n3'}
681 def _real_extract(self
, url
):
682 station
= self
._match
_id
(url
)
683 area
= self
._configuration
_arg
('area', ['tokyo'])[0]
685 config
= self
._download
_xml
(
686 'https://www.nhk.or.jp/radio/config/config_web.xml', station
, 'Downloading area information')
687 data
= config
.find(f
'.//data//area[.="{area}"]/..')
690 raise ExtractorError('Invalid area. Valid areas are: %s' % ', '.join(
691 [i
.text
for i
in config
.findall('.//data//area')]), expected
=True)
693 noa_info
= self
._download
_json
(
694 f
'https:{config.find(".//url_program_noa").text}'.format(area
=data
.find('areakey').text
),
695 station
, note
=f
'Downloading {area} station metadata', fatal
=False)
696 present_info
= traverse_obj(noa_info
, ('nowonair_list', self
._NOA
_STATION
_IDS
.get(station
), 'present'))
699 'title': ' '.join(traverse_obj(present_info
, (('service', 'area',), 'name', {str}
))),
700 'id': join_nonempty(station
, area
),
701 'thumbnails': traverse_obj(present_info
, ('service', 'images', ..., {
703 'width': ('width', {int_or_none}
),
704 'height': ('height', {int_or_none}
),
706 'formats': self
._extract
_m
3u8_formats
(data
.find(f
'{station}hls').text
, station
),