[extractor/nbc] Add NBCStations extractor (#5077)

Closes #4571
Authored by: bashonly
This commit is contained in:
bashonly 2022-09-30 15:43:40 +00:00 committed by GitHub
parent dfea94f8f6
commit 11398b922c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 173 additions and 0 deletions

View File

@ -1079,6 +1079,7 @@
NBCSportsIE, NBCSportsIE,
NBCSportsStreamIE, NBCSportsStreamIE,
NBCSportsVPlayerIE, NBCSportsVPlayerIE,
NBCStationsIE,
) )
from .ndr import ( from .ndr import (
NDRIE, NDRIE,

View File

@ -7,14 +7,20 @@
from .adobepass import AdobePassIE from .adobepass import AdobePassIE
from ..compat import compat_urllib_parse_unquote from ..compat import compat_urllib_parse_unquote
from ..utils import ( from ..utils import (
ExtractorError,
int_or_none, int_or_none,
parse_age_limit, parse_age_limit,
parse_duration, parse_duration,
RegexNotFoundError, RegexNotFoundError,
smuggle_url, smuggle_url,
str_or_none,
traverse_obj,
try_get, try_get,
unified_strdate,
unified_timestamp, unified_timestamp,
update_url_query, update_url_query,
url_basename,
variadic,
) )
@ -584,3 +590,169 @@ def _real_extract(self, url):
'formats': formats, 'formats': formats,
'is_live': is_live, 'is_live': is_live,
} }
class NBCStationsIE(InfoExtractor):
_DOMAIN_RE = '|'.join(map(re.escape, (
'nbcbayarea', 'nbcboston', 'nbcchicago', 'nbcconnecticut', 'nbcdfw', 'nbclosangeles',
'nbcmiami', 'nbcnewyork', 'nbcphiladelphia', 'nbcsandiego', 'nbcwashington',
'necn', 'telemundo52', 'telemundoarizona', 'telemundochicago', 'telemundonuevainglaterra',
)))
_VALID_URL = rf'https?://(?:www\.)?(?P<site>{_DOMAIN_RE})\.com/(?:[^/?#]+/)*(?P<id>[^/?#]+)/?(?:$|[#?])'
_TESTS = [{
'url': 'https://www.nbclosangeles.com/news/local/large-structure-fire-in-downtown-la-prompts-smoke-odor-advisory/2968618/',
'md5': '462041d91bd762ef5a38b7d85d6dc18f',
'info_dict': {
'id': '2968618',
'ext': 'mp4',
'title': 'Large Structure Fire in Downtown LA Prompts Smoke Odor Advisory',
'description': None,
'timestamp': 1661135892,
'upload_date': '20220821',
'uploader': 'NBC 4',
'uploader_id': 'KNBC',
'channel': 'nbclosangeles',
},
}, {
'url': 'https://www.telemundoarizona.com/responde/huracan-complica-reembolso-para-televidente-de-tucson/2247002/',
'md5': '0917dcf7885be1023a9220630d415f67',
'info_dict': {
'id': '2247002',
'ext': 'mp4',
'title': 'Huracán complica que televidente de Tucson reciba reembolso',
'description': 'md5:af298dc73aab74d4fca6abfb12acb6cf',
'timestamp': 1660886507,
'upload_date': '20220819',
'uploader': 'Telemundo Arizona',
'uploader_id': 'KTAZ',
'channel': 'telemundoarizona',
},
}]
_RESOLUTIONS = {
'1080': '1920',
'720': '1280',
'540': '960',
'360': '640',
'234': '416',
}
def _real_extract(self, url):
channel, video_id = self._match_valid_url(url).group('site', 'id')
webpage = self._download_webpage(url, video_id)
nbc_data = self._search_json(
r'<script>var\s*nbc\s*=\s*', webpage, 'NBC JSON data', video_id)
pdk_acct = nbc_data.get('pdkAcct') or 'Yh1nAC'
fw_ssid = traverse_obj(nbc_data, ('video', 'fwSSID'))
fw_network_id = traverse_obj(nbc_data, ('video', 'fwNetworkID'), default='382114')
video_data = self._parse_json(self._html_search_regex(
r'data-videos="([^"]*)"', webpage, 'video data', default='{}'), video_id)
video_data = variadic(video_data)[0]
video_data.update(self._parse_json(self._html_search_regex(
r'data-meta="([^"]*)"', webpage, 'metadata', default='{}'), video_id))
formats = []
if video_data.get('mpx_is_livestream') == '1':
live = True
player_id = traverse_obj(
video_data, 'mpx_m3upid', ('video', 'meta', 'mpx_m3upid'), 'mpx_pid',
('video', 'meta', 'mpx_pid'), 'pid_streaming_web_medium')
query = {
'mbr': 'true',
'assetTypes': 'LegacyRelease',
'fwsitesection': fw_ssid,
'fwNetworkID': fw_network_id,
'pprofile': 'ots_desktop_html',
'sensitive': 'false',
'w': '1920',
'h': '1080',
'rnd': '1660303',
'mode': 'LIVE',
'format': 'SMIL',
'tracking': 'true',
'formats': 'M3U+none,MPEG-DASH+none,MPEG4,MP3',
'vpaid': 'script',
'schema': '2.0',
'SDK': 'PDK+6.1.3',
}
info = {
'title': f'{channel} livestream',
}
else:
live = False
player_id = traverse_obj(
video_data, ('video', 'meta', 'pid_streaming_web_high'), 'pid_streaming_web_high',
('video', 'meta', 'mpx_pid'), 'mpx_pid')
date_string = traverse_obj(video_data, 'date_string', 'date_gmt')
if date_string:
date_string = self._search_regex(
r'datetime="([^"]+)"', date_string, 'date string', fatal=False)
else:
date_string = traverse_obj(
nbc_data, ('dataLayer', 'adobe', 'prop70'), ('dataLayer', 'adobe', 'eVar70'),
('dataLayer', 'adobe', 'eVar59'))
video_url = traverse_obj(video_data, ('video', 'meta', 'mp4_url'), 'mp4_url')
if video_url:
height = url_basename(video_url).split('-')[1].split('p')[0]
formats.append({
'url': video_url,
'ext': 'mp4',
'width': int_or_none(self._RESOLUTIONS.get(height)),
'height': int_or_none(height),
'format_id': f'http-{height}',
})
query = {
'mbr': 'true',
'assetTypes': 'LegacyRelease',
'fwsitesection': fw_ssid,
'fwNetworkID': fw_network_id,
'format': 'redirect',
'manifest': 'm3u',
'Tracking': 'true',
'Embedded': 'true',
'formats': 'MPEG4',
}
info = {
'title': video_data.get('title') or traverse_obj(
nbc_data, ('dataLayer', 'contenttitle'), ('dataLayer', 'title'),
('dataLayer', 'adobe', 'prop22'), ('dataLayer', 'id')),
'description': traverse_obj(video_data, 'summary', 'excerpt', 'video_hero_text'),
'upload_date': str_or_none(unified_strdate(date_string)),
'timestamp': int_or_none(unified_timestamp(date_string)),
}
if not player_id:
raise ExtractorError(
'No video player ID or livestream player ID found in webpage', expected=True)
headers = {'Origin': f'https://www.{channel}.com'}
manifest, urlh = self._download_webpage_handle(
f'https://link.theplatform.com/s/{pdk_acct}/{player_id}', video_id,
headers=headers, query=query, note='Downloading manifest')
if live:
manifest_url = self._search_regex(r'<video src="([^"]*)', manifest, 'manifest URL')
else:
manifest_url = urlh.geturl()
formats.extend(self._extract_m3u8_formats(
manifest_url, video_id, 'mp4', headers=headers, m3u8_id='hls',
fatal=live, live=live, errnote='No HLS formats found'))
self._sort_formats(formats)
return {
'id': str_or_none(video_id),
'channel': channel,
'uploader': str_or_none(nbc_data.get('on_air_name')),
'uploader_id': str_or_none(nbc_data.get('callLetters')),
'formats': formats,
'is_live': live,
**info,
}