Última actividad 1754673639

YouTube MP3 Downloader

youtube_downloader.py Sin formato
1import tkinter as tk
2from tkinter import ttk
3from tkinter import filedialog
4from tkinter import messagebox
5import yt_dlp
6import os
7import re
8from urllib.parse import urlparse, parse_qs
9import webbrowser
10from PIL import Image, ImageTk
11import sys
12from ttkthemes import ThemedTk
13import logging
14import traceback
15
16# Set up logging
17logging.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
26class 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
350if __name__ == "__main__":
351 root = ThemedTk(theme="breeze")
352 app = YouTubeDownloader(root)
353 app.run()