urocibg zrewidował ten Gist . Przejdź do rewizji
1 file changed, 353 insertions
youtube_downloader.py(stworzono plik)
@@ -0,0 +1,353 @@ | |||
1 | + | import tkinter as tk | |
2 | + | from tkinter import ttk | |
3 | + | from tkinter import filedialog | |
4 | + | from tkinter import messagebox | |
5 | + | import yt_dlp | |
6 | + | import os | |
7 | + | import re | |
8 | + | from urllib.parse import urlparse, parse_qs | |
9 | + | import webbrowser | |
10 | + | from PIL import Image, ImageTk | |
11 | + | import sys | |
12 | + | from ttkthemes import ThemedTk | |
13 | + | import logging | |
14 | + | import traceback | |
15 | + | ||
16 | + | # Set up logging | |
17 | + | logging.basicConfig( | |
18 | + | level=logging.DEBUG, | |
19 | + | format='%(asctime)s - %(levelname)s - %(message)s', | |
20 | + | handlers=[ | |
21 | + | logging.FileHandler('youtube_downloader.log'), | |
22 | + | logging.StreamHandler() | |
23 | + | ] | |
24 | + | ) | |
25 | + | ||
26 | + | class YouTubeDownloader: | |
27 | + | def __init__(self, root): | |
28 | + | try: | |
29 | + | self.root = root | |
30 | + | self.root.title("fedya-mp3") | |
31 | + | self.root.geometry("650x400") | |
32 | + | self.root.resizable(True, True) | |
33 | + | ||
34 | + | # Icon setup with better error handling | |
35 | + | try: | |
36 | + | icon_paths = [ | |
37 | + | 'icon.ico', | |
38 | + | 'C:/Users/fedia/Desktop/mp3/icon.ico', | |
39 | + | os.path.join(os.path.dirname(__file__), 'icon.ico') | |
40 | + | ] | |
41 | + | ||
42 | + | for icon_path in icon_paths: | |
43 | + | if os.path.exists(icon_path): | |
44 | + | self.root.iconbitmap(icon_path) | |
45 | + | logging.info(f"Successfully loaded icon from: {icon_path}") | |
46 | + | break | |
47 | + | else: | |
48 | + | logging.warning("No icon file found in any of the searched locations") | |
49 | + | except Exception as e: | |
50 | + | logging.error(f"Error loading icon: {str(e)}") | |
51 | + | ||
52 | + | self.notebook = ttk.Notebook(self.root) | |
53 | + | self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) | |
54 | + | ||
55 | + | self.download_frame = ttk.Frame(self.notebook) | |
56 | + | self.notebook.add(self.download_frame, text="Сваляне") | |
57 | + | ||
58 | + | self.about_frame = ttk.Frame(self.notebook) | |
59 | + | self.notebook.add(self.about_frame, text="За програмата") | |
60 | + | ||
61 | + | self.main_frame = ttk.Frame(self.download_frame, padding="10") | |
62 | + | self.main_frame.pack(fill=tk.BOTH, expand=True) | |
63 | + | ||
64 | + | self.create_download_tab() | |
65 | + | self.create_about_tab() | |
66 | + | ||
67 | + | except Exception as e: | |
68 | + | logging.error(f"Error in initialization: {str(e)}\n{traceback.format_exc()}") | |
69 | + | messagebox.showerror("Error", f"Error initializing application: {str(e)}") | |
70 | + | ||
71 | + | def create_download_tab(self): | |
72 | + | try: | |
73 | + | # URL секция | |
74 | + | self.create_url_section() | |
75 | + | ||
76 | + | # Дестинация секция | |
77 | + | self.create_destination_section() | |
78 | + | ||
79 | + | # Прогрес секция | |
80 | + | self.create_progress_section() | |
81 | + | ||
82 | + | # Бутони за действие | |
83 | + | self.create_action_buttons() | |
84 | + | ||
85 | + | # Статус | |
86 | + | self.status_label = ttk.Label(self.main_frame, | |
87 | + | text="Готов за сваляне", | |
88 | + | font=('Helvetica', 10)) | |
89 | + | self.status_label.pack(pady=5) | |
90 | + | except Exception as e: | |
91 | + | logging.error(f"Error creating download tab: {str(e)}") | |
92 | + | raise | |
93 | + | ||
94 | + | def create_url_section(self): | |
95 | + | url_frame = ttk.LabelFrame(self.main_frame, text="URL адрес", padding="5") | |
96 | + | url_frame.pack(fill=tk.X, pady=5) | |
97 | + | ||
98 | + | self.url_entry = ttk.Entry(url_frame, width=70) | |
99 | + | self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) | |
100 | + | ||
101 | + | paste_button = tk.Button(url_frame, | |
102 | + | text="Постави URL", | |
103 | + | command=self.paste_url, | |
104 | + | bg='#2196F3', | |
105 | + | fg='white', | |
106 | + | relief=tk.RAISED, | |
107 | + | borderwidth=1, | |
108 | + | cursor='hand2') | |
109 | + | paste_button.pack(side=tk.RIGHT, padx=5) | |
110 | + | ||
111 | + | def create_destination_section(self): | |
112 | + | dest_frame = ttk.LabelFrame(self.main_frame, text="Дестинация", padding="5") | |
113 | + | dest_frame.pack(fill=tk.X, pady=5) | |
114 | + | ||
115 | + | self.destination_var = tk.StringVar(value=os.getcwd()) | |
116 | + | self.destination_entry = ttk.Entry(dest_frame, textvariable=self.destination_var) | |
117 | + | self.destination_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) | |
118 | + | ||
119 | + | dest_button = tk.Button(dest_frame, | |
120 | + | text="Избери папка", | |
121 | + | command=self.choose_destination, | |
122 | + | bg='#FF9800', | |
123 | + | fg='white', | |
124 | + | relief=tk.RAISED, | |
125 | + | borderwidth=1, | |
126 | + | cursor='hand2') | |
127 | + | dest_button.pack(side=tk.RIGHT, padx=5) | |
128 | + | ||
129 | + | def create_progress_section(self): | |
130 | + | progress_frame = ttk.Frame(self.main_frame) | |
131 | + | progress_frame.pack(fill=tk.X, pady=10) | |
132 | + | ||
133 | + | self.progress_var = tk.DoubleVar() | |
134 | + | self.progress_bar = ttk.Progressbar( | |
135 | + | progress_frame, | |
136 | + | variable=self.progress_var, | |
137 | + | maximum=100, | |
138 | + | mode='determinate', | |
139 | + | length=300 | |
140 | + | ) | |
141 | + | self.progress_bar.pack(fill=tk.X) | |
142 | + | ||
143 | + | def create_action_buttons(self): | |
144 | + | button_frame = ttk.Frame(self.main_frame) | |
145 | + | button_frame.pack(fill=tk.X, pady=5) | |
146 | + | ||
147 | + | download_button = tk.Button( | |
148 | + | button_frame, | |
149 | + | text="Изтегли", | |
150 | + | command=self.start_download, | |
151 | + | bg='#4CAF50', | |
152 | + | fg='white', | |
153 | + | relief=tk.RAISED, | |
154 | + | borderwidth=1, | |
155 | + | width=15, | |
156 | + | cursor='hand2', | |
157 | + | font=('Helvetica', 10, 'bold') | |
158 | + | ) | |
159 | + | download_button.pack(side=tk.LEFT, padx=5) | |
160 | + | ||
161 | + | cancel_button = tk.Button( | |
162 | + | button_frame, | |
163 | + | text="Отказ", | |
164 | + | command=self.cancel_download, | |
165 | + | bg='#f44336', | |
166 | + | fg='white', | |
167 | + | relief=tk.RAISED, | |
168 | + | borderwidth=1, | |
169 | + | width=15, | |
170 | + | cursor='hand2', | |
171 | + | font=('Helvetica', 10) | |
172 | + | ) | |
173 | + | cancel_button.pack(side=tk.LEFT, padx=5) | |
174 | + | ||
175 | + | for button in (download_button, cancel_button): | |
176 | + | button.bind('<Enter>', lambda e, b=button: self.on_hover(e, b)) | |
177 | + | button.bind('<Leave>', lambda e, b=button: self.on_leave(e, b)) | |
178 | + | ||
179 | + | def create_about_tab(self): | |
180 | + | center_frame = ttk.Frame(self.about_frame) | |
181 | + | center_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20) | |
182 | + | ||
183 | + | title_label = ttk.Label(center_frame, | |
184 | + | text="YouTube MP3 Downloader", | |
185 | + | font=('Helvetica', 16, 'bold')) | |
186 | + | title_label.pack(pady=(0,10)) | |
187 | + | ||
188 | + | version_label = ttk.Label(center_frame, | |
189 | + | text="Версия 1.2.0", | |
190 | + | font=('Helvetica', 10)) | |
191 | + | version_label.pack() | |
192 | + | ||
193 | + | description = """YouTube MP3 Downloader е безплатна програма за сваляне на аудио от YouTube. | |
194 | + | ||
195 | + | Програмата позволява лесно и бързо конвертиране на видеа в MP3 формат. | |
196 | + | ||
197 | + | Характеристики: | |
198 | + | - Лесен и интуитивен интерфейс | |
199 | + | - Бързо сваляне и конвертиране | |
200 | + | - Високо качество на звука (192kbps) | |
201 | + | - Поддръжка на плейлисти | |
202 | + | ||
203 | + | Важно предупреждение: | |
204 | + | Този софтуер е създаден само с образователна цел. | |
205 | + | """ | |
206 | + | ||
207 | + | desc_text = tk.Text(center_frame, wrap=tk.WORD, height=10, width=50) | |
208 | + | desc_text.insert('1.0', description) | |
209 | + | desc_text.configure(state='disabled', border=0, font=('Helvetica', 10)) | |
210 | + | desc_text.pack(pady=20, padx=20) | |
211 | + | ||
212 | + | website_label = ttk.Label(center_frame, | |
213 | + | text="Посетете нашия сайт", | |
214 | + | font=('Helvetica', 10, 'underline'), | |
215 | + | foreground='blue', | |
216 | + | cursor='hand2') | |
217 | + | website_label.pack(pady=10) | |
218 | + | website_label.bind('<Button-1>', lambda e: webbrowser.open('https://urocibg.eu/')) | |
219 | + | ||
220 | + | copyright_label = ttk.Label(center_frame, | |
221 | + | text="© 2024 Всички права запазени", | |
222 | + | font=('Helvetica', 8)) | |
223 | + | copyright_label.pack(side=tk.BOTTOM, pady=5) | |
224 | + | ||
225 | + | def validate_youtube_url(self, url): | |
226 | + | if not url: | |
227 | + | return False | |
228 | + | ||
229 | + | patterns = [ | |
230 | + | r'^https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[\w-]+', | |
231 | + | r'^https?:\/\/(?:www\.)?youtu\.be\/[\w-]+', | |
232 | + | r'^https?:\/\/(?:www\.)?youtube\.com\/playlist\?list=[\w-]+' | |
233 | + | ] | |
234 | + | ||
235 | + | return any(re.match(pattern, url) for pattern in patterns) | |
236 | + | ||
237 | + | def progress_hook(self, d): | |
238 | + | if d['status'] == 'downloading': | |
239 | + | try: | |
240 | + | progress = (d['downloaded_bytes'] / d['total_bytes']) * 100 | |
241 | + | self.progress_var.set(progress) | |
242 | + | self.status_label.config(text=f"Сваляне: {progress:.1f}%") | |
243 | + | self.root.update() | |
244 | + | except: | |
245 | + | pass | |
246 | + | elif d['status'] == 'finished': | |
247 | + | self.status_label.config(text="Конвертиране в MP3...") | |
248 | + | self.root.update() | |
249 | + | ||
250 | + | def paste_url(self): | |
251 | + | try: | |
252 | + | clipboard_text = self.root.clipboard_get() | |
253 | + | self.url_entry.delete(0, tk.END) | |
254 | + | self.url_entry.insert(0, clipboard_text) | |
255 | + | except tk.TclError: | |
256 | + | messagebox.showwarning("Предупреждение", "Няма текст в клипборда") | |
257 | + | ||
258 | + | def choose_destination(self): | |
259 | + | chosen_folder = filedialog.askdirectory() | |
260 | + | if chosen_folder: | |
261 | + | self.destination_var.set(chosen_folder) | |
262 | + | ||
263 | + | def on_hover(self, event, button): | |
264 | + | if button['bg'] == '#4CAF50': | |
265 | + | button.configure(bg='#45a049') | |
266 | + | elif button['bg'] == '#f44336': | |
267 | + | button.configure(bg='#da190b') | |
268 | + | elif button['bg'] == '#2196F3': | |
269 | + | button.configure(bg='#1976D2') | |
270 | + | elif button['bg'] == '#FF9800': | |
271 | + | button.configure(bg='#F57C00') | |
272 | + | ||
273 | + | def on_leave(self, event, button): | |
274 | + | if button['bg'] in ['#45a049']: | |
275 | + | button.configure(bg='#4CAF50') | |
276 | + | elif button['bg'] in ['#da190b']: | |
277 | + | button.configure(bg='#f44336') | |
278 | + | elif button['bg'] in ['#1976D2']: | |
279 | + | button.configure(bg='#2196F3') | |
280 | + | elif button['bg'] in ['#F57C00']: | |
281 | + | button.configure(bg='#FF9800') | |
282 | + | ||
283 | + | def start_download(self): | |
284 | + | try: | |
285 | + | url = self.url_entry.get().strip() | |
286 | + | logging.info(f"Starting download for URL: {url}") | |
287 | + | ||
288 | + | if not self.validate_youtube_url(url): | |
289 | + | logging.warning(f"Invalid URL attempted: {url}") | |
290 | + | messagebox.showerror("Грешка", "Невалиден YouTube URL адрес") | |
291 | + | return | |
292 | + | ||
293 | + | destination = self.destination_var.get() | |
294 | + | if not os.path.exists(destination): | |
295 | + | logging.error(f"Invalid destination path: {destination}") | |
296 | + | messagebox.showerror("Грешка", "Невалидна дестинация") | |
297 | + | return | |
298 | + | ||
299 | + | self.progress_var.set(0) | |
300 | + | self.status_label.config(text="Започва сваляне...") | |
301 | + | ||
302 | + | ydl_opts = { | |
303 | + | 'format': 'bestaudio/best', | |
304 | + | 'postprocessors': [{ | |
305 | + | 'key': 'FFmpegExtractAudio', | |
306 | + | 'preferredcodec': 'mp3', | |
307 | + | 'preferredquality': '192', | |
308 | + | }], | |
309 | + | 'outtmpl': os.path.join(destination, '%(title)s.%(ext)s'), | |
310 | + | 'progress_hooks': [self.progress_hook], | |
311 | + | 'no_warnings': False, | |
312 | + | 'quiet': False, | |
313 | + | 'verbose': True | |
314 | + | } | |
315 | + | ||
316 | + | logging.info("Starting download with yt-dlp") | |
317 | + | with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
318 | + | ydl.download([url]) | |
319 | + | ||
320 | + | self.progress_var.set(100) | |
321 | + | self.status_label.config(text="Готово!") | |
322 | + | logging.info("Download completed successfully") | |
323 | + | messagebox.showinfo("Успех", "Файлът беше успешно свален и конвертиран в MP3 формат.") | |
324 | + | ||
325 | + | except yt_dlp.utils.DownloadError as e: | |
326 | + | logging.error(f"yt-dlp download error: {str(e)}\n{traceback.format_exc()}") | |
327 | + | self.status_label.config(text="Грешка при сваляне") | |
328 | + | messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}") | |
329 | + | except Exception as e: | |
330 | + | logging.error(f"Unexpected error: {str(e)}\n{traceback.format_exc()}") | |
331 | + | self.status_label.config(text="Грешка при сваляне") | |
332 | + | messagebox.showerror("Грешка", f"Неочаквана грешка: {str(e)}") | |
333 | + | finally: | |
334 | + | self.progress_var.set(0) | |
335 | + | ||
336 | + | def cancel_download(self): | |
337 | + | self.status_label.config(text="Свалянето е отказано") | |
338 | + | self.progress_var.set(0) | |
339 | + | ||
340 | + | def run(self): | |
341 | + | window_width = 650 | |
342 | + | window_height = 400 | |
343 | + | screen_width = self.root.winfo_screenwidth() | |
344 | + | screen_height = self.root.winfo_screenheight() | |
345 | + | center_x = int(screen_width/2 - window_width/2) | |
346 | + | center_y = int(screen_height/2 - window_height/2) | |
347 | + | self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}') | |
348 | + | self.root.mainloop() | |
349 | + | ||
350 | + | if __name__ == "__main__": | |
351 | + | root = ThemedTk(theme="breeze") | |
352 | + | app = YouTubeDownloader(root) | |
353 | + | app.run() |
Nowsze
Starsze