[ie/niconico] Support "--load-info-json" by saving WebSocket url

aka "--load-info".

Don't save a Response object to info JSON. Just create a new WebSocket
  connection during the download. Due to Niconico's logic, the manifest
  m3u8 url will be unusable soon if there is no active WebSocket
  connection, so the reconnection will give us a valid manifest m3u8,
  unless the WebSocket url has already expired.
This commit is contained in:
Mozi 2024-03-12 15:34:37 +00:00
parent 972a2d51ad
commit d9a6507fe6
2 changed files with 34 additions and 34 deletions

View File

@ -79,33 +79,33 @@ def _ws_context(self, info_dict):
video_id = info_dict['id'] video_id = info_dict['id']
live_latency = info_dict['downloader_options']['live_latency'] live_latency = info_dict['downloader_options']['live_latency']
self.ws = info_dict['downloader_options']['ws'] ws_url = info_dict['downloader_options']['ws_url']
self.ws = None
self.m3u8_lock = threading.Event() self.m3u8_lock = threading.Event()
self.m3u8_url = info_dict['manifest_url'] self.m3u8_url = None
self.m3u8_lock.set()
def communicate_ws(reconnect): def communicate_ws():
if reconnect: self.ws = self.ydl.urlopen(Request(ws_url, headers=info_dict.get('http_headers')))
self.ws = self.ydl.urlopen(Request(self.ws.url, headers=info_dict.get('http_headers'))) if self.ydl.params.get('verbose', False):
if self.ydl.params.get('verbose', False): self.to_screen('[debug] Sending startWatching request')
self.to_screen('[debug] Sending startWatching request') self.ws.send(json.dumps({
self.ws.send(json.dumps({ 'type': 'startWatching',
'type': 'startWatching', 'data': {
'data': { 'stream': {
'stream': { 'quality': 'abr',
'quality': 'abr', 'protocol': 'hls',
'protocol': 'hls', 'latency': live_latency,
'latency': live_latency, 'chasePlay': False
'chasePlay': False },
}, 'room': {
'room': { 'protocol': 'webSocket',
'protocol': 'webSocket', 'commentable': True
'commentable': True },
}, 'reconnect': True,
'reconnect': True, }
} }))
}))
with self.ws: with self.ws:
while True: while True:
recv = self.ws.recv() recv = self.ws.recv()
@ -136,10 +136,9 @@ def communicate_ws(reconnect):
stopped = threading.Event() stopped = threading.Event()
def ws_main(): def ws_main():
reconnect = False
while not stopped.is_set(): while not stopped.is_set():
try: try:
communicate_ws(reconnect) communicate_ws()
break # Disconnected break # Disconnected
except BaseException as e: # Including TransportError except BaseException as e: # Including TransportError
if stopped.is_set(): if stopped.is_set():
@ -150,8 +149,6 @@ def ws_main():
self.to_screen('[%s] %s: Connection error occured, reconnecting after %d seconds: %s' % ('niconico:live', video_id, self._WEBSOCKET_RECONNECT_DELAY, str_or_none(e))) self.to_screen('[%s] %s: Connection error occured, reconnecting after %d seconds: %s' % ('niconico:live', video_id, self._WEBSOCKET_RECONNECT_DELAY, str_or_none(e)))
time.sleep(self._WEBSOCKET_RECONNECT_DELAY) time.sleep(self._WEBSOCKET_RECONNECT_DELAY)
reconnect = True
self.m3u8_lock.set() # Release possible locks self.m3u8_lock.set() # Release possible locks
thread = threading.Thread(target=ws_main, daemon=True) thread = threading.Thread(target=ws_main, daemon=True)

View File

@ -956,7 +956,10 @@ class NiconicoLiveIE(InfoExtractor):
_KNOWN_LATENCY = ('high', 'low') _KNOWN_LATENCY = ('high', 'low')
def _yield_formats(self, ws, video_id, latency, is_live): def _yield_formats(self, ws_url, headers, latency, video_id, is_live):
ws = self._request_webpage(
Request(ws_url, headers=headers), video_id, note='Connecting to WebSocket server')
self.write_debug('[debug] Sending HLS server request') self.write_debug('[debug] Sending HLS server request')
ws.send(json.dumps({ ws.send(json.dumps({
'type': 'startWatching', 'type': 'startWatching',
@ -998,6 +1001,8 @@ def _yield_formats(self, ws, video_id, latency, is_live):
recv = recv[:100] + '...' recv = recv[:100] + '...'
self.write_debug('Server said: %s' % recv) self.write_debug('Server said: %s' % recv)
ws.close()
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=is_live) formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=is_live)
for fmt, q in zip(formats, reversed(qualities[1:])): for fmt, q in zip(formats, reversed(qualities[1:])):
fmt.update({ fmt.update({
@ -1014,14 +1019,11 @@ def _real_extract(self, url):
embedded_data = self._parse_json(unescapeHTML(self._search_regex( embedded_data = self._parse_json(unescapeHTML(self._search_regex(
r'<script\s+id="embedded-data"\s*data-props="(.+?)"', webpage, 'embedded data')), video_id) r'<script\s+id="embedded-data"\s*data-props="(.+?)"', webpage, 'embedded data')), video_id)
ws = None
ws_url = traverse_obj(embedded_data, ('site', 'relive', 'webSocketUrl')) ws_url = traverse_obj(embedded_data, ('site', 'relive', 'webSocketUrl'))
if ws_url: if ws_url:
ws_url = update_url_query(ws_url, { ws_url = update_url_query(ws_url, {
'frontend_id': traverse_obj(embedded_data, ('site', 'frontendId')) or '9', 'frontend_id': traverse_obj(embedded_data, ('site', 'frontendId')) or '9',
}) })
ws = self._request_webpage(
Request(ws_url, headers=headers), video_id, note='Connecting to WebSocket server')
else: else:
self.raise_no_formats('The live hasn\'t started yet or already ended.', expected=True) self.raise_no_formats('The live hasn\'t started yet or already ended.', expected=True)
@ -1074,10 +1076,11 @@ def _real_extract(self, url):
'timestamp': int_or_none(traverse_obj(embedded_data, ('program', 'openTime'))), 'timestamp': int_or_none(traverse_obj(embedded_data, ('program', 'openTime'))),
'live_status': live_status, 'live_status': live_status,
'thumbnails': thumbnails, 'thumbnails': thumbnails,
'formats': [*self._yield_formats(ws, video_id, latency, live_status == 'is_live')] if ws else None, 'formats': [*self._yield_formats(
ws_url, headers, latency, video_id, live_status == 'is_live')] if ws_url else None,
'http_headers': headers, 'http_headers': headers,
'downloader_options': { 'downloader_options': {
'live_latency': latency, 'live_latency': latency,
'ws': ws, 'ws_url': ws_url,
}, },
} }