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