66from ffmpeg import ensure_ffmpeg_ready
77from PyQt6 .QtWidgets import (
88 QApplication , QMainWindow , QPushButton , QProgressBar , QLabel , QLineEdit , QSlider , QCheckBox , QComboBox , QDoubleSpinBox ,
9- QMessageBox , QTableWidget , QTableWidgetItem , QVBoxLayout , QWidget , QHBoxLayout , QToolButton , QSpinBox , QGraphicsView , QTextEdit
9+ QMessageBox , QTableWidget , QTableWidgetItem , QVBoxLayout , QWidget , QToolButton , QSpinBox , QGraphicsView , QTextEdit
1010)
1111from PyQt6 import uic , QtWidgets
12- from PyQt6 .QtGui import QClipboard , QPixmap , QImage
12+ from PyQt6 .QtGui import QPixmap
1313from PyQt6 .QtCore import Qt , QThread , pyqtSignal , QObject , QTimer
14- from io import BytesIO
1514import re
1615import time
1716
1817
19- URL = "https://youtu.be/njX2bu-_Vw4 ?si=DpqFGMkWUOU56A5g" #DEBUG - Remove in production
18+ URL = "https://youtu.be/jZhjqMMV1B4 ?si=QeTmdVjiTt-_xnuo"
2019video_info = {}
2120extensions = {}
2221ERROR = False
@@ -36,6 +35,21 @@ def update_options(**kwargs):
3635 else :
3736 ydl_opts [key ] = value
3837
38+ class ImageDownloader (QThread ):
39+ image_downloaded = pyqtSignal (bytes )
40+
41+ def __init__ (self , url , parent = None ):
42+ super ().__init__ (parent )
43+ self .url = url
44+
45+ def run (self ):
46+ try :
47+ response = requests .get (self .url )
48+ response .raise_for_status ()
49+ self .image_downloaded .emit (response .content )
50+ except requests .exceptions .RequestException as e :
51+ print (f"Error downloading image: { e } " )
52+
3953class AdvancedWindow (QWidget ):
4054 def __init__ (self ):
4155 super ().__init__ ()
@@ -48,54 +62,73 @@ def __init__(self):
4862 ydl_opts .clear ()
4963 self .qSlider = self .findChild (QSlider , 'qSlider' )
5064 self .qSliderLabel = self .findChild (QLabel , 'qSliderLabel' )
51-
5265 self .checkVerbose = self .findChild (QCheckBox , 'checkVerbose' )
5366 self .checkSubtitle = self .findChild (QCheckBox , 'checkSubtitles' )
5467 self .checkMetadata = self .findChild (QCheckBox , 'checkMetadata' )
55-
5668 self .extensionBox = self .findChild (QComboBox , 'extensionBox' )
5769 self .retryBox = self .findChild (QSpinBox , 'retryBox' )
5870 self .checkRate = self .findChild (QCheckBox , 'checkRate' )
5971 self .rateLimiterBox = self .findChild (QDoubleSpinBox , 'rateLimiterBox' )
60- self .rateLimiterBox .hide ()
61- #TODO -> Rate LImiter doesnt work
6272 self .graphicsView = self .findChild (QGraphicsView , 'graphicsView' )
6373 self .pushConfirm = self .findChild (QPushButton , 'pushConfirm' )
6474 self .pushReset = self .findChild (QPushButton , 'pushReset' )
6575 self .pushConfirm .clicked .connect (self .confirm )
76+ self .pushReset .clicked .connect (self .close )
77+
78+ self .scene = QtWidgets .QGraphicsScene (self )
79+ self .graphicsView .setScene (self .scene )
6680
6781 self .qSlider .valueChanged .connect (self .update_slider_label )
6882
69- #Fetch Thumbnail
7083 global thumbnail_url
71- image_data = BytesIO (requests .get (thumbnail_url ).content )
72- image = QImage ()
73- image .loadFromData (image_data .getvalue ())
74- pixmap = QPixmap .fromImage (image )
75- view_size = self .graphicsView .size ()
76- scaled_pixed = pixmap .scaled (view_size ,Qt .AspectRatioMode .IgnoreAspectRatio , Qt .TransformationMode .SmoothTransformation )
77- scene = QtWidgets .QGraphicsScene ()
78- scene .addPixmap (scaled_pixed )
79- self .graphicsView .setScene (scene )
84+ if thumbnail_url :
85+ self .image_downloader = ImageDownloader (thumbnail_url )
86+ self .image_downloader .image_downloaded .connect (self .set_image )
87+ self .image_downloader .start ()
88+
89+ def set_image (self , image_data ):
90+ pixmap = QPixmap ()
91+ pixmap .loadFromData (image_data )
92+ self .scene .addPixmap (pixmap )
93+ self .graphicsView .fitInView (self .scene .itemsBoundingRect (), Qt .AspectRatioMode .KeepAspectRatio )
8094
8195 def confirm (self ):
82- current_quality = self .qSliderLabel .text ()
83- print (f"Selected Quality: { current_quality } " )
84- current_retries = self .retryBox .value ()
85- print (f"Selected number of retries: { current_retries } " )
86- current_ext = self .extensionBox .currentText ()
87- print (f"Selected Extension: { current_ext } \n " )
96+ val = self .qSlider .value ()
97+
98+ if val >= 75 :
99+ fmt = 'bestvideo+bestaudio/best'
100+ elif val >= 50 :
101+ fmt = 'bestvideo[height<=1080]+bestaudio/best[height<=1080]'
102+ elif val >= 25 :
103+ fmt = 'bestvideo[height<=720]+bestaudio/best[height<=720]'
104+ else :
105+ fmt = 'bestvideo[height<=360]+bestaudio/best[height<=360]'
106+
107+ new_opts = {
108+ 'format' : fmt ,
109+ 'retries' : self .retryBox .value (),
110+ 'merge_output_format' : self .extensionBox .currentText (),
111+ }
112+ if self .checkSubtitle .isChecked ():
113+ new_opts .update ({
114+ 'writesubtitles' : True ,
115+ 'writeautomaticsub' : True ,
116+ 'subtitlesformat' : 'srt/best' ,
117+ 'subtitleslangs' : ['en' ]
118+ })
119+ update_options (** new_opts )
120+ self .close ()
88121
89122 def update_slider_label (self ):
90123 current_value = self .qSlider .value ()
91- labels = ["Worst " , "Good " , "Better " , "Best" ]
124+ labels = ["<=360p " , "720p " , "1080p " , "Best" ]
92125 index = current_value // 25
93126 self .qSliderLabel .setText (labels [index ])
94127
95128class DownloadWorker (QThread ):
96129 progress_update = pyqtSignal (str ,int )
97130 download_complete = pyqtSignal ()
98- sleep_for_item = pyqtSignal (str , int ) # New signal: title, duration
131+ sleep_for_item = pyqtSignal (str , int )
99132
100133 def __init__ (self ,selected_videos ,logger_var = None ):
101134 super ().__init__ ()
@@ -113,7 +146,7 @@ def run(self):
113146 global extensions
114147 try :
115148 for title ,url in self .selected_videos :
116- self .current_download_title = title # Set the current title
149+ self .current_download_title = title
117150 ext = "mp4"
118151 if extensions [title ]== False :
119152 ext = "mp3"
@@ -129,12 +162,11 @@ def run(self):
129162
130163 def download_video_with_progress (self , title , url , ext ):
131164 global ydl_opts
132- ydl_opts .clear ()
133165 ffmpeg_args = {'ffmpeg_location' : './ffmpeg' } if sys .platform == "win32" else {}
134166 try :
135- update_options (format = 'bestvideo+bestaudio/best' , outtmpl = f'{ title } .{ ext } ' ,progress_hooks = [lambda d : self .update_progress (d ,title )], no_warnings = False , logger = self .logger_var2 , ** ffmpeg_args )
167+ update_options (outtmpl = f'{ title } .{ ext } ' ,progress_hooks = [lambda d : self .update_progress (d ,title )], no_warnings = False , logger = self .logger_var2 , ** ffmpeg_args )
136168 except Exception :
137- update_options (format = 'bestvideo+bestaudio/best' , outtmpl = f'{ title } .{ ext } ' ,progress_hooks = [lambda d : self .update_progress (d ,title )], no_warnings = False , ** ffmpeg_args )
169+ update_options (outtmpl = f'{ title } .{ ext } ' ,progress_hooks = [lambda d : self .update_progress (d ,title )], no_warnings = False , ** ffmpeg_args )
138170 with yt_dlp .YoutubeDL (ydl_opts ) as ydl :
139171 try :
140172 ydl .download ([url ])
@@ -174,7 +206,7 @@ def log_error(self, message):
174206
175207class Logger (QObject ):
176208 messageSignal = pyqtSignal (str )
177- sleep_detected = pyqtSignal (str , int ) # New signal: title, duration
209+ sleep_detected = pyqtSignal (str , int )
178210
179211 def debug (self ,msg ):
180212 self .messageSignal .emit (msg )
@@ -192,8 +224,8 @@ def _check_for_sleep_message(self, msg):
192224 match = re .search (r"^\[download\] Sleeping (\d+\.?\d*) seconds as required by the site\.\.\." , msg )
193225 if match :
194226 duration = int (float (match .group (1 )))
195- # We'll use a placeholder for now, actual title will be handled in DownloadWorker
196227 self .sleep_detected .emit ("GLOBAL_SLEEP" , duration )
228+
197229class ResultWindow (QWidget ):
198230 def __init__ (self ,results ):
199231 super ().__init__ ()
@@ -209,31 +241,26 @@ def __init__(self,results):
209241 self .sleep_timers = {}
210242 self .current_sleep_durations = {}
211243 for row , (title , ext ) in enumerate (results ):
212- #CheckBox Item Setup
213244 checkbox_item = QTableWidgetItem ()
214245 checkbox_item .setFlags (checkbox_item .flags () | Qt .ItemFlag .ItemIsUserCheckable )
215246 checkbox_item .setCheckState (Qt .CheckState .Unchecked )
216247 self .table .setItem (row , 0 , checkbox_item )
217248 self .table .item (row , 0 ).setFlags (self .table .item (row , 0 ).flags () & ~ Qt .ItemFlag .ItemIsEditable )
218249
219- #Title Item Setup
220250 self .table .setItem (row , 1 , QTableWidgetItem (title ))
221251 self .table .item (row , 1 ).setFlags (self .table .item (row , 1 ).flags () & ~ Qt .ItemFlag .ItemIsEditable )
222252
223- #Toggle Button Setup
224253 toggle_button = QPushButton ('Video (mp4)' )
225254 toggle_button .setCheckable (True )
226255 extensions [title ]= True
227256 toggle_button .clicked .connect (lambda checked , btn = toggle_button , r = row : self .toggle_value (btn ,r ))
228257 self .table .setCellWidget (row , 2 , toggle_button )
229258
230- #Progress Bars Setup
231259 progress_bar = QProgressBar ()
232260 progress_bar .setValue (0 )
233261 self .progress_bars [title ]= progress_bar
234262 self .table .setCellWidget (row , 3 , progress_bar )
235263
236- #Logger Setup
237264 logger = QTextEdit ()
238265 logger .setReadOnly (True )
239266 self .loggers [title ]= logger
@@ -243,7 +270,6 @@ def __init__(self,results):
243270 self .downloadButtonPlayList = QPushButton ("Download Selected" )
244271 self .downloadButtonPlayList .clicked .connect (self .search_and_download )
245272
246- #Table Config
247273 self .table .setAlternatingRowColors (True )
248274 self .table .setColumnWidth (0 ,25 )
249275 self .table .setColumnWidth (1 ,200 )
@@ -286,7 +312,7 @@ def search_and_download(self):
286312 self .worker = DownloadWorker (self .selected_videos ,self .logger_var )
287313 self .worker .progress_update .connect (self .update_progress )
288314 self .worker .download_complete .connect (self .on_download_complete )
289- self .worker .sleep_for_item .connect (self .handle_sleep_countdown ) # Connect new signal
315+ self .worker .sleep_for_item .connect (self .handle_sleep_countdown )
290316 self .worker .start ()
291317
292318 def handle_sleep_countdown (self , title , duration ):
@@ -299,14 +325,14 @@ def handle_sleep_countdown(self, title, duration):
299325 logger = self .loggers .get (title )
300326 if progress_bar and logger :
301327 progress_bar .setMaximum (duration )
302- progress_bar .setValue (duration ) # Start at max for reverse progress
328+ progress_bar .setValue (duration )
303329 logger .clear ()
304330 logger .insertPlainText (f"Sleeping { duration } seconds..." )
305331
306332 self .current_sleep_durations [title ] = duration
307333 timer = QTimer (self )
308- timer .timeout .connect (lambda : self ._update_sleep_progress (title )) # Use lambda to pass title
309- timer .start (1000 ) # Update every second
334+ timer .timeout .connect (lambda : self ._update_sleep_progress (title ))
335+ timer .start (1000 )
310336 self .sleep_timers [title ] = timer
311337
312338 def _update_sleep_progress (self , title ):
@@ -326,16 +352,16 @@ def _update_sleep_progress(self, title):
326352 self .sleep_timers [title ].stop ()
327353 del self .sleep_timers [title ]
328354 del self .current_sleep_durations [title ]
329- progress_bar .setValue (0 ) # Reset to 0 after sleep
355+ progress_bar .setValue (0 )
330356 logger .clear ()
331- logger .insertPlainText ('Downloading...' ) # Or whatever the next status is
357+ logger .insertPlainText ('Downloading...' )
332358
333359 def update_progress (self ,title ,percent ):
334360 if title in self .progress_bars :
335361 self .progress_bars [title ].setValue (percent )
336362 if percent >= 0 :
337363 self .loggers [title ].clear ()
338- self .loggers [title ].insertPlainText ('Downloading...' )
364+ self .loggers [title ].insertPlainText (f" { percent } % Complete" )
339365
340366 def on_download_complete (self ):
341367 global ERROR
0 commit comments