]> jfr.im git - yt-dlp.git/blame - yt_dlp/downloader/niconico.py
[cleanup] Add more ruff rules (#10149)
[yt-dlp.git] / yt_dlp / downloader / niconico.py
CommitLineData
f8f9250f 1import json
fb198a8a 2import threading
f8f9250f 3import time
fb198a8a 4
c487cf00 5from . import get_suitable_downloader
fb198a8a 6from .common import FileDownloader
f8f9250f 7from .external import FFmpegFD
3d2623a8 8from ..networking import Request
ccfd70f4 9from ..utils import DownloadError, str_or_none, try_get
fb198a8a 10
11
12class NiconicoDmcFD(FileDownloader):
13 """ Downloading niconico douga from DMC with heartbeat """
14
fb198a8a 15 def real_download(self, filename, info_dict):
c487cf00 16 from ..extractor.niconico import NiconicoIE
fb198a8a 17
add96eb9 18 self.to_screen(f'[{self.FD_NAME}] Downloading from DMC')
fb198a8a 19 ie = NiconicoIE(self.ydl)
20 info_dict, heartbeat_info_dict = ie._get_heartbeat_info(info_dict)
21
dbf5416a 22 fd = get_suitable_downloader(info_dict, params=self.params)(self.ydl, self.params)
fb198a8a 23
24 success = download_complete = False
25 timer = [None]
fb198a8a 26 heartbeat_lock = threading.Lock()
27 heartbeat_url = heartbeat_info_dict['url']
2291dbce 28 heartbeat_data = heartbeat_info_dict['data'].encode()
fb198a8a 29 heartbeat_interval = heartbeat_info_dict.get('interval', 30)
fb198a8a 30
3d2623a8 31 request = Request(heartbeat_url, heartbeat_data)
ee2b3563 32
fb198a8a 33 def heartbeat():
34 try:
ee2b3563 35 self.ydl.urlopen(request).read()
fb198a8a 36 except Exception:
add96eb9 37 self.to_screen(f'[{self.FD_NAME}] Heartbeat failed')
fb198a8a 38
39 with heartbeat_lock:
40 if not download_complete:
41 timer[0] = threading.Timer(heartbeat_interval, heartbeat)
42 timer[0].start()
43
2291dbce 44 heartbeat_info_dict['ping']()
45 self.to_screen('[%s] Heartbeat with %d second interval ...' % (self.FD_NAME, heartbeat_interval))
fb198a8a 46 try:
47 heartbeat()
2291dbce 48 if type(fd).__name__ == 'HlsFD':
49 info_dict.update(ie._extract_m3u8_formats(info_dict['url'], info_dict['id'])[0])
fb198a8a 50 success = fd.real_download(filename, info_dict)
51 finally:
52 if heartbeat_lock:
53 with heartbeat_lock:
54 timer[0].cancel()
55 download_complete = True
0f06bcd7 56 return success
f8f9250f
L
57
58
59class NiconicoLiveFD(FileDownloader):
60 """ Downloads niconico live without being stopped """
61
62 def real_download(self, filename, info_dict):
63 video_id = info_dict['video_id']
64 ws_url = info_dict['url']
65 ws_extractor = info_dict['ws']
66 ws_origin_host = info_dict['origin']
f8f9250f
L
67 live_quality = info_dict.get('live_quality', 'high')
68 live_latency = info_dict.get('live_latency', 'high')
69 dl = FFmpegFD(self.ydl, self.params or {})
70
71 new_info_dict = info_dict.copy()
72 new_info_dict.update({
73 'protocol': 'm3u8',
74 })
75
76 def communicate_ws(reconnect):
77 if reconnect:
ccfd70f4 78 ws = self.ydl.urlopen(Request(ws_url, headers={'Origin': f'https://{ws_origin_host}'}))
f8f9250f
L
79 if self.ydl.params.get('verbose', False):
80 self.to_screen('[debug] Sending startWatching request')
81 ws.send(json.dumps({
82 'type': 'startWatching',
83 'data': {
84 'stream': {
85 'quality': live_quality,
86 'protocol': 'hls+fmp4',
87 'latency': live_latency,
add96eb9 88 'chasePlay': False,
f8f9250f
L
89 },
90 'room': {
91 'protocol': 'webSocket',
add96eb9 92 'commentable': True,
f8f9250f
L
93 },
94 'reconnect': True,
add96eb9 95 },
f8f9250f
L
96 }))
97 else:
98 ws = ws_extractor
99 with ws:
100 while True:
101 recv = ws.recv()
102 if not recv:
103 continue
104 data = json.loads(recv)
105 if not data or not isinstance(data, dict):
106 continue
107 if data.get('type') == 'ping':
108 # pong back
109 ws.send(r'{"type":"pong"}')
110 ws.send(r'{"type":"keepSeat"}')
111 elif data.get('type') == 'disconnect':
112 self.write_debug(data)
113 return True
114 elif data.get('type') == 'error':
115 self.write_debug(data)
116 message = try_get(data, lambda x: x['body']['code'], str) or recv
117 return DownloadError(message)
118 elif self.ydl.params.get('verbose', False):
119 if len(recv) > 100:
120 recv = recv[:100] + '...'
add96eb9 121 self.to_screen(f'[debug] Server said: {recv}')
f8f9250f
L
122
123 def ws_main():
124 reconnect = False
125 while True:
126 try:
127 ret = communicate_ws(reconnect)
128 if ret is True:
129 return
130 except BaseException as e:
add96eb9 131 self.to_screen('[{}] {}: Connection error occured, reconnecting after 10 seconds: {}'.format('niconico:live', video_id, str_or_none(e)))
f8f9250f
L
132 time.sleep(10)
133 continue
134 finally:
135 reconnect = True
136
137 thread = threading.Thread(target=ws_main, daemon=True)
138 thread.start()
139
140 return dl.download(filename, new_info_dict)