22import subprocess
33from tkinter import Tk , filedialog
44from tqdm import tqdm
5+ import threading
6+ from queue import Queue
57
68
79# Function to select a file using a file dialog
@@ -26,22 +28,105 @@ def select_directory():
2628 return dir_path
2729
2830
31+ # Function to download a single video
32+ def download_video (video_url , cookies_file , download_location , progress_queue ):
33+ cmd = [
34+ "yt-dlp" ,
35+ "--cookies" ,
36+ cookies_file ,
37+ "-f" ,
38+ "bestvideo+bestaudio/best" ,
39+ "-o" ,
40+ os .path .join (download_location , "%(title)s.%(ext)s" ),
41+ video_url ,
42+ ]
43+
44+ try :
45+ process = subprocess .Popen (
46+ cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True
47+ )
48+
49+ for line in process .stdout :
50+ if "%" in line :
51+ try :
52+ percent = int (line .split ("%" )[0 ].split ()[- 1 ])
53+ progress_queue .put ((video_url , percent ))
54+ except (ValueError , IndexError ):
55+ pass
56+
57+ process .wait ()
58+ if process .returncode == 0 :
59+ progress_queue .put ((video_url , 100 ))
60+ else :
61+ raise subprocess .CalledProcessError (process .returncode , cmd )
62+
63+ except subprocess .CalledProcessError as e :
64+ print (f"Failed to download { video_url } . Error: { e } \n " )
65+ progress_queue .put ((video_url , - 1 ))
66+
67+
68+ # Function to retry failed downloads
69+ def retry_failed_downloads (failed_videos , cookies_file , download_location , retries , log_file ):
70+ for attempt in range (1 , retries + 1 ):
71+ if not failed_videos :
72+ break
73+
74+ print (f"\n Retry attempt { attempt } /{ retries } for failed downloads...\n " )
75+ progress_queue = Queue ()
76+ threads = []
77+
78+ for video_url in failed_videos :
79+ thread = threading .Thread (
80+ target = download_video ,
81+ args = (video_url , cookies_file , download_location , progress_queue ),
82+ )
83+ threads .append (thread )
84+ thread .start ()
85+
86+ for thread in threads :
87+ thread .join ()
88+
89+ results = collect_results (progress_queue )
90+ failed_videos = [url for url , status in results .items () if status != 100 ]
91+
92+ with open (log_file , "a" ) as log :
93+ log .write (f"\n Retry attempt { attempt } results:\n " )
94+ for url , status in results .items ():
95+ log .write (f"{ url } - { 'Success' if status == 100 else 'Failed' } \n " )
96+
97+ return failed_videos
98+
99+
100+ # Function to collect results from the progress queue
101+ def collect_results (progress_queue ):
102+ results = {}
103+ while not progress_queue .empty ():
104+ video_url , status = progress_queue .get ()
105+ results [video_url ] = status
106+ return results
107+
108+
29109# Main function
30110def main ():
31- # Initialize Tkinter and hide the main window
32111 root = Tk ()
33112 root .withdraw ()
34113
35- # Select the cookies file
36- cookies_file = select_file ( "cookies.txt" )
114+ # Configuration defaults
115+ default_cookies = "cookies.txt"
37116
38- # Select the video links file
117+ # Always select video_links.txt using a file dialog
118+ cookies_file = select_file ("cookies.txt" )
39119 video_links_file = select_file ("video_links.txt" )
40120
41- # Select the download location
121+ # Always use the file dialog to select the download location
42122 download_location = select_directory ()
43123
44- # Read the video links from the file
124+ # Ask for retry limit
125+ retries = input ("Enter the number of retry attempts for failed downloads (default is 3): " ).strip ()
126+ retries = int (retries ) if retries .isdigit () else 3
127+ log_file = os .path .join (download_location , "download_log.txt" )
128+
129+ # Read video links
45130 if not os .path .exists (video_links_file ):
46131 print (f"Error: File '{ video_links_file } ' not found." )
47132 exit (1 )
@@ -55,53 +140,51 @@ def main():
55140
56141 print (f"Found { len (video_links )} video(s) to download.\n " )
57142
58- for idx , video_url in enumerate (video_links , start = 1 ):
59- print (f"Downloading video { idx } /{ len (video_links )} : { video_url } " )
60- try :
61- with tqdm (total = 100 , desc = f"Downloading { video_url } " , unit = "%" , ncols = 80 ) as pbar :
62- # yt-dlp command
63- cmd = [
64- "yt-dlp" ,
65- "--cookies" ,
66- cookies_file ,
67- "-f" ,
68- "bestvideo+bestaudio/best" ,
69- "-o" ,
70- f"{ download_location } /%(title)s.%(ext)s" ,
71- video_url ,
72- ]
73-
74- # Run yt-dlp as a subprocess and capture output
75- process = subprocess .Popen (
76- cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True
77- )
78-
79- # Read yt-dlp output to update progress
80- for line in process .stdout :
81- if "%" in line :
82- try :
83- # Extract percentage from yt-dlp output
84- percent = int (line .split ("%" )[0 ].split ()[- 1 ])
85- pbar .n = percent
86- pbar .last_print_n = percent # Avoid overcounting
87- pbar .refresh ()
88- except (ValueError , IndexError ):
89- pass
90-
91- process .wait ()
92-
93- # Check if the process completed successfully
94- if process .returncode == 0 :
95- print (f"\n Successfully downloaded: { video_url } \n " )
96- else :
97- raise subprocess .CalledProcessError (process .returncode , cmd )
98-
99- except subprocess .CalledProcessError as e :
100- print (f"Failed to download { video_url } . Error: { e } \n " )
101-
102- print ("All done!" )
103-
104-
105- # Run the main function
143+ # Start downloads
144+ progress_queue = Queue ()
145+ threads = []
146+
147+ for video_url in video_links :
148+ thread = threading .Thread (
149+ target = download_video ,
150+ args = (video_url , cookies_file , download_location , progress_queue ),
151+ )
152+ threads .append (thread )
153+ thread .start ()
154+
155+ for thread in threads :
156+ thread .join ()
157+
158+ # Collect and retry failed downloads
159+ results = collect_results (progress_queue )
160+ failed_videos = [url for url , status in results .items () if status != 100 ]
161+
162+ if failed_videos :
163+ failed_videos = retry_failed_downloads (
164+ failed_videos , cookies_file , download_location , retries , log_file
165+ )
166+
167+ # Summarize results
168+ print ("\n Download Summary:" )
169+ success_count = sum (1 for status in results .values () if status == 100 )
170+ print (f"Successfully downloaded { success_count } /{ len (video_links )} videos." )
171+
172+ if failed_videos :
173+ print (f"Failed to download { len (failed_videos )} video(s):" )
174+ for video in failed_videos :
175+ print (f" - { video } " )
176+
177+ # Log final results
178+ with open (log_file , "a" ) as log :
179+ log .write ("\n Final Download Summary:\n " )
180+ for video_url , status in results .items ():
181+ log .write (f"{ video_url } - { 'Success' if status == 100 else 'Failed' } \n " )
182+ if failed_videos :
183+ log .write ("\n Failed Videos:\n " )
184+ for video in failed_videos :
185+ log .write (f"{ video } \n " )
186+ print (f"\n Log saved at { log_file } " )
187+
188+
106189if __name__ == "__main__" :
107190 main ()
0 commit comments