1
1
import sys
2
+ import platform
2
3
3
4
import click
4
- from click import confirm
5
+ import inquirer
5
6
import typer
6
7
from typing_extensions import Annotated
7
8
from typing import Optional
8
- from rich .console import Console
9
9
from urllib .parse import urlparse
10
10
from pathlib import Path
11
+ import pylibimport
11
12
12
13
from api .api import NCP , ContentCode
13
14
from util .ffmpeg import FFMPEG
14
15
from util .m3u8_downloader import M3U8Downloader
15
16
from util .channel_downloader import ChannelDownloader
17
+ from util .progress import ProgressManager
18
+
19
+ __import__ ('util.inquirer_console_render' ) # hook for inquirer console render
16
20
17
21
18
22
class Resolution (click .ParamType ):
@@ -71,14 +75,6 @@ def main(
71
75
help = 'Resume download.' ,
72
76
),
73
77
] = None ,
74
- experimental : Annotated [
75
- Optional [bool ],
76
- typer .Option (
77
- '--experimental/--normal' , '-e/-ne' ,
78
- show_default = False ,
79
- help = 'Experimental download method' ,
80
- ),
81
- ] = None ,
82
78
yes : Annotated [
83
79
Optional [bool ],
84
80
typer .Option (
@@ -130,9 +126,17 @@ def main(
130
126
typer .Option (
131
127
'--thread' ,
132
128
show_default = True ,
133
- help = 'Number of threads.' ,
129
+ help = 'Number of threads. Be careful with this option. ' ,
134
130
),
135
131
] = 1 ,
132
+ select_manually : Annotated [
133
+ bool ,
134
+ typer .Option (
135
+ '--select-manually' ,
136
+ show_default = False ,
137
+ help = 'Select which video to download manually. This option only works with channel.' ,
138
+ ),
139
+ ] = False ,
136
140
username : Annotated [
137
141
str ,
138
142
typer .Option (
@@ -157,10 +161,8 @@ def main(
157
161
] = False ,
158
162
) -> None :
159
163
"""The NCP Downloader"""
160
- err_console = Console (stderr = True )
161
-
162
- # nico
163
- nico = NCP (urlparse (query ).netloc , username , password )
164
+ api_client = NCP (urlparse (query ).netloc , username , password )
165
+ progress_manager = ProgressManager ()
164
166
165
167
try :
166
168
# Check ffmpeg if transcode is enabled
@@ -170,60 +172,83 @@ def main(
170
172
171
173
# If yes is enabled, skip all confirmation
172
174
if yes :
173
- experimental = resume = yes
175
+ resume = yes
174
176
175
177
# tell user multithreading is dengerous
176
- if (thread > 1 and
177
- not confirm ('Download with multithreading may got you banned from Server. Continue?' , default = False )):
178
- raise RuntimeError ('Aborted.' )
178
+ # can not be skipped by --yes
179
+ if thread > 1 :
180
+ with progress_manager .pause ():
181
+ if inquirer .prompt ([inquirer .List ('thread' ,
182
+ message = 'Multithreading is dangerous, are you sure to continue?' ,
183
+ choices = ['Yes' , 'No' ], default = 'No' )],
184
+ raise_keyboard_interrupt = True )['thread' ] == 'No' :
185
+ raise RuntimeError ('Aborted.' )
179
186
180
187
# Check if query is channel or video
181
- if nico .get_channel_id (query ) is None :
188
+ if api_client .get_channel_id (query ) is None :
182
189
query = urlparse (query ).path .strip ('/' ).split ('/' )[- 1 ]
183
- session_id = nico .get_session_id (ContentCode (query ))
190
+ session_id = api_client .get_session_id (ContentCode (query ))
184
191
185
192
# Check if video exists
186
193
if session_id is None :
187
194
raise ValueError ('Video not found or permission denied.' )
188
195
189
- output_name , _ = nico .get_video_name (ContentCode (query ))
196
+ output_name , _ = api_client .get_video_name (ContentCode (query ))
190
197
191
198
output = str (Path (output ).joinpath (output_name ))
192
199
193
- M3U8Downloader (nico , session_id , output , resolution , resume , transcode ,
194
- ffmpeg , vcodec , acodec , ffmpeg_options , thread )
200
+ with progress_manager :
201
+ m3u8_downloader = M3U8Downloader (api_client , progress_manager , session_id , output , resolution , resume ,
202
+ transcode , ffmpeg , vcodec , acodec , ffmpeg_options , thread )
203
+ if not m3u8_downloader .start ():
204
+ raise RuntimeError ('Failed to download video.' )
195
205
else :
206
+ # warning for downloading whole channel if --yes is not set
196
207
if not yes :
197
- if not confirm ('Sure to download whole channel?' , default = True ):
198
- raise RuntimeError ('Aborted.' )
199
-
200
- if experimental is None :
201
- experimental = confirm ('Using experimental download method?' , default = True )
208
+ with progress_manager .pause ():
209
+ if inquirer .prompt ([
210
+ inquirer .List ('channel' , message = 'Sure to download whole channel?' ,
211
+ choices = ['Sure' , 'No' ], default = 'No' )
212
+ ], raise_keyboard_interrupt = True )['channel' ] == 'No' :
213
+ raise RuntimeError ('Aborted.' )
202
214
203
215
# Get channel infomation
204
- channel_id = nico .get_channel_id (query )
205
- channel_name = nico .get_channel_info (channel_id )['fanclub_site_name' ]
216
+ channel_id = api_client .get_channel_id (query )
217
+ channel_name = api_client .get_channel_info (channel_id )['fanclub_site_name' ]
206
218
207
219
# Get video list
208
- if experimental :
209
- video_list = nico .list_videos_x (channel_id )
210
- else :
211
- video_list = nico .list_videos (channel_id )
212
- video_list = [ContentCode (video ['content_code' ]) for video in video_list ]
220
+ video_list = api_client .list_videos (channel_id )
221
+ video_list = [ContentCode (video ['content_code' ]) for video in video_list ]
213
222
214
223
output = str (Path (output ).joinpath (channel_name ))
215
224
216
- ChannelDownloader (nico , channel_id , video_list , output , resolution , resume ,
217
- transcode , ffmpeg , vcodec , acodec , ffmpeg_options , thread )
225
+ with progress_manager :
226
+ channel_downloader = ChannelDownloader (api_client , progress_manager , channel_id , video_list , output ,
227
+ resolution , resume ,
228
+ transcode , ffmpeg , vcodec , acodec , ffmpeg_options ,
229
+ thread , select_manually )
230
+ channel_downloader .start ()
218
231
except Exception as e :
219
232
# Raise exception again if debug is enabled
220
233
if debug :
221
234
raise e
222
235
# Print error message and exit if debug is disabled
223
236
else :
224
- err_console . print (f'[red] { e } [/ red] ' )
237
+ progress_manager . live . console . print (f'{ e } ' , style = ' red' )
225
238
sys .exit (1 )
226
239
227
240
228
241
if __name__ == "__main__" :
242
+ # find all the .pyd(win) or .so(linux and macos), files in the current directory
243
+ if platform .system () == 'Windows' :
244
+ pyds = Path ('.' ).glob ('*.pyd' )
245
+ elif platform .system () == 'Linux' or platform .system () == 'Darwin' :
246
+ pyds = Path ('.' ).glob ('*.so' )
247
+ else :
248
+ raise RuntimeError ('Unsupported platform' )
249
+
250
+ # import all the .pyd or .so files
251
+ for pyd in pyds :
252
+ pylibimport .import_module (pyd .stem )
253
+
229
254
typer .run (main )
0 commit comments