3
3
import os .path
4
4
5
5
6
+ from pytube import YouTube
6
7
from threading import Thread
7
- from tkinter import filedialog , messagebox
8
+ from tkinter import filedialog , messagebox , ttk
8
9
from download_youtube_video import download_youtube_video
9
10
from pytube .exceptions import PytubeError , RegexMatchError
10
11
@@ -14,8 +15,10 @@ class YouTubeDownloadGUI(tk.Frame):
14
15
def __init__ (self , master = None ):
15
16
super ().__init__ (master )
16
17
self .pack ()
18
+ self .label_video_title = None
17
19
self .btn_download = None
18
20
self .btn_output_browse = None
21
+ self .btn_check_id = None
19
22
self .text_url = None
20
23
self .text_output_path = None
21
24
self .text_filename_override = None
@@ -25,15 +28,25 @@ def __init__(self, master=None):
25
28
self .output_path = tk .StringVar (self )
26
29
self .filename_override = tk .StringVar (self )
27
30
self .proxy = tk .StringVar (self )
31
+ self .video = None
32
+ self .stream = tk .IntVar (self )
33
+ self .streams = []
34
+ self .stream_widgets = []
35
+ self .file_size = 0
36
+ self .progress_bar = None
28
37
29
- self .quit = None
38
+ self .last_row = 0
30
39
31
40
self .create_widgets ()
32
41
33
42
def create_widgets (self ):
34
43
tk .Label (self , text = 'YouTube URL/ID' ).grid (row = 0 , column = 0 )
35
44
self .text_url = tk .Entry (self , width = 60 )
36
45
self .text_url .grid (row = 0 , column = 1 , columnspan = 2 )
46
+ self .btn_check_id = tk .Button (self )
47
+ self .btn_check_id ['text' ] = 'Check Video'
48
+ self .btn_check_id ['command' ] = self .check_video
49
+ self .btn_check_id .grid (row = 0 , column = 3 )
37
50
38
51
tk .Label (self , text = 'Output Directory' ).grid (row = 1 , column = 0 )
39
52
self .text_output_path = tk .Entry (self , width = 60 , textvariable = self .output_path )
@@ -51,43 +64,107 @@ def create_widgets(self):
51
64
self .text_proxy = tk .Entry (self , width = 60 , textvariable = self .proxy )
52
65
self .text_proxy .grid (row = 3 , column = 1 , columnspan = 2 )
53
66
54
- self .radio_video_audio .append (tk .Radiobutton (self , text = 'Video' , variable = self .audio_only , value = False ))
67
+ tk .Label (self , text = 'Media Type' ).grid (row = 4 , column = 0 )
68
+ self .radio_video_audio .append (tk .Radiobutton (self , text = 'Video' , variable = self .audio_only ,
69
+ value = False , command = self .check_video ))
55
70
self .radio_video_audio .append (tk .Radiobutton (self , text = 'Audio (Takes Longer)' , variable = self .audio_only ,
56
- value = True ))
57
-
71
+ value = True , command = self .check_video ))
58
72
self .radio_video_audio [0 ].grid (row = 4 , column = 1 )
59
73
self .radio_video_audio [1 ].grid (row = 4 , column = 2 )
60
74
61
- self .btn_download = tk .Button (self )
62
- self .btn_download ['text' ] = 'Download'
63
- self .btn_download ['command' ] = self .download
64
- self .btn_download .grid (row = 5 , column = 1 , columnspan = 2 )
65
-
66
- self .quit = tk .Button (self , text = "QUIT" , fg = "red" ,
67
- command = root .destroy )
68
- self .quit .grid (row = 6 , column = 1 , columnspan = 2 )
75
+ self .label_video_title = tk .Label (self )
76
+ self .label_video_title .grid (row = 5 , column = 0 , columnspan = 4 )
77
+ self .last_row = 5
69
78
70
79
def browse_output_path (self ):
71
80
self .output_path .set (filedialog .askdirectory (initialdir = '/' , title = 'Select Output Folder' ))
72
- self .text_output_path .delete (0 , tk .END )
73
- self .text_output_path .insert (0 , self .output_path .get ())
81
+
82
+ def check_video (self ):
83
+ self .btn_check_id ['text' ] = 'Checking...'
84
+ self .btn_check_id .config (state = tk .DISABLED )
85
+ Thread (target = self .threaded_check_video ).start ()
86
+
87
+ def threaded_check_video (self ):
88
+ self .last_row = 5
89
+ self .stream .set (0 )
90
+ [radio_button .destroy () for radio_button in self .stream_widgets ]
91
+ if self .btn_download :
92
+ self .progress_bar .destroy ()
93
+ self .btn_download .destroy ()
94
+ url = self .text_url .get ()
95
+ if 'https' not in url :
96
+ url = 'https://www.youtube.com/watch?v=%s' % url
97
+ try :
98
+ if self .proxy .get () != '' :
99
+ self .video = YouTube (url , proxies = {self .proxy .get ().split (':' )[0 ]: self .proxy .get ()})
100
+ else :
101
+ self .video = YouTube (url )
102
+ self .label_video_title ['text' ] = self .video .title
103
+ self .streams = self .video .streams .filter (only_audio = self .audio_only .get ()).all ()
104
+
105
+ for stream in self .streams :
106
+ if self .audio_only .get ():
107
+ text = f'Codec: { stream .audio_codec } , ' \
108
+ f'ABR: { stream .abr } ' \
109
+ f'File Type: { stream .mime_type .split ("/" )[1 ]} , Size: { stream .filesize // 1024 } KB'
110
+ else :
111
+ if stream .video_codec is None :
112
+ continue
113
+ text = f'Res: { stream .resolution } , FPS: { stream .fps } ,' \
114
+ f' Video Codec: { stream .video_codec } , Audio Codec: { stream .audio_codec } , ' \
115
+ f'File Type: { stream .mime_type .split ("/" )[1 ]} , Size: { stream .filesize // 1024 } KB'
116
+ radio_button = tk .Radiobutton (self , text = text , variable = self .stream , value = stream .itag )
117
+ self .last_row += 1
118
+ radio_button .grid (row = self .last_row , column = 0 , columnspan = 4 )
119
+ self .stream_widgets .append (radio_button )
120
+ self .last_row += 1
121
+ self .progress_bar = ttk .Progressbar (self , orient = 'horizontal' , length = 350 , mode = 'determinate' )
122
+ self .progress_bar .grid (row = self .last_row , column = 1 , columnspan = 2 )
123
+
124
+ self .progress_bar ['value' ] = 0
125
+ self .progress_bar ['maximum' ] = 100
126
+
127
+ self .last_row += 1
128
+ self .btn_download = tk .Button (self )
129
+ self .btn_download ['text' ] = 'Download'
130
+ self .btn_download ['command' ] = self .download
131
+ self .btn_download .config (state = tk .NORMAL )
132
+ self .btn_download .grid (row = self .last_row , column = 1 , columnspan = 2 )
133
+ except PytubeError as e :
134
+ messagebox .showerror ('Something went wrong...' , e )
135
+ except RegexMatchError as e :
136
+ messagebox .showerror ('Something went wrong...' , e )
137
+ finally :
138
+ self .btn_check_id ['text' ] = 'Check Video'
139
+ self .btn_check_id .config (state = tk .NORMAL )
74
140
75
141
def download (self ):
76
142
self .btn_download ['text' ] = 'Downloading...'
77
143
self .btn_download .config (state = tk .DISABLED )
144
+ self .btn_check_id .config (state = tk .DISABLED )
145
+ self .btn_output_browse .config (state = tk .DISABLED )
146
+ [radio_button .config (state = tk .DISABLED ) for radio_button in self .radio_video_audio ]
78
147
Thread (target = self .threaded_download ).start ()
79
148
149
+ def update_progress_bar (self , stream , chunk , file_handle , bytes_remaining ):
150
+ percentage = ((self .file_size - bytes_remaining ) / self .file_size ) * 100
151
+ self .progress_bar ['value' ] = percentage
152
+
80
153
def threaded_download (self ):
81
154
try :
82
155
if self .proxy .get () != '' :
83
156
proxy = {self .proxy .get ().split (':' )[0 ]: self .proxy .get ()}
84
157
else :
85
158
proxy = None
86
- filename = download_youtube_video (self .text_url .get (), audio_only = self .audio_only .get (),
159
+ for search_stream in self .streams :
160
+ if int (search_stream .itag ) == int (self .stream .get ()):
161
+ self .file_size = search_stream .filesize
162
+ break
163
+ filename = download_youtube_video (self .text_url .get (), itag = self .stream .get (),
87
164
output_path = self .output_path .get (),
88
165
filename = self .filename_override .get ()
89
166
if self .filename_override .get () != '' else None ,
90
- proxies = proxy )
167
+ proxies = proxy , progress_callback = self . update_progress_bar )
91
168
messagebox .showinfo ('Download Complete!' , 'Download Complete!\n %s' % filename )
92
169
except PytubeError as e :
93
170
messagebox .showerror ('Something went wrong...' , e )
@@ -100,11 +177,21 @@ def threaded_download(self):
100
177
finally :
101
178
self .btn_download ['text' ] = 'Download'
102
179
self .btn_download .config (state = tk .NORMAL )
180
+ self .btn_check_id .config (state = tk .NORMAL )
181
+ self .btn_output_browse .config (state = tk .NORMAL )
182
+ [radio_button .config (state = tk .NORMAL ) for radio_button in self .radio_video_audio ]
183
+
184
+
185
+ def resource_path (relative_path ):
186
+ import sys
187
+ if hasattr (sys , '_MEIPASS' ):
188
+ return os .path .join (sys ._MEIPASS , relative_path )
189
+ return os .path .join (os .path .abspath ('.' ), relative_path )
103
190
104
191
105
192
if __name__ == '__main__' :
106
193
root = tk .Tk ()
107
194
app = YouTubeDownloadGUI (master = root )
108
195
app .master .title ('YouTube Video/Audio Downloader' )
109
- app .master .tk .call ('wm' , 'iconphoto' , app .master ._w , tk .PhotoImage (file = os . path . abspath ('assets/ytdl.png' )))
196
+ app .master .tk .call ('wm' , 'iconphoto' , app .master ._w , tk .PhotoImage (file = resource_path ('assets/ytdl.png' )))
110
197
app .mainloop ()
0 commit comments