Skip to content

Commit 8a4a509

Browse files
Update download_videos_manual.py
Added retry logic for failed downloads with configurable retry attempts. Enhanced support for selecting video_links.txt and cookies.txt through file dialogs. Improved error handling to ensure the script handles invalid paths and empty input files gracefully. Optimized progress tracking using the tqdm library for better user feedback during downloads. Refactored code for better readability and maintainability. This update improves reliability and user experience, making the script more robust and user-friendly.
1 parent 69f0640 commit 8a4a509

1 file changed

Lines changed: 137 additions & 54 deletions

File tree

download_videos_manual.py

Lines changed: 137 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import subprocess
33
from tkinter import Tk, filedialog
44
from 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"\nRetry 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"\nRetry 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
30110
def 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"\nSuccessfully 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("\nDownload 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("\nFinal 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("\nFailed Videos:\n")
184+
for video in failed_videos:
185+
log.write(f"{video}\n")
186+
print(f"\nLog saved at {log_file}")
187+
188+
106189
if __name__ == "__main__":
107190
main()

0 commit comments

Comments
 (0)