最終更新 1754673639

YouTube MP3 Downloader

urocibg revised this gist 1754673639. Go to revision

1 file changed, 353 insertions

youtube_downloader.py(file created)

@@ -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()
Newer Older