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