youtube_downloader.py
· 14 KiB · Python
Raw
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
import yt_dlp
import os
import re
from urllib.parse import urlparse, parse_qs
import webbrowser
from PIL import Image, ImageTk
import sys
from ttkthemes import ThemedTk
import logging
import traceback
# Set up logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('youtube_downloader.log'),
logging.StreamHandler()
]
)
class YouTubeDownloader:
def __init__(self, root):
try:
self.root = root
self.root.title("fedya-mp3")
self.root.geometry("650x400")
self.root.resizable(True, True)
# Icon setup with better error handling
try:
icon_paths = [
'icon.ico',
'C:/Users/fedia/Desktop/mp3/icon.ico',
os.path.join(os.path.dirname(__file__), 'icon.ico')
]
for icon_path in icon_paths:
if os.path.exists(icon_path):
self.root.iconbitmap(icon_path)
logging.info(f"Successfully loaded icon from: {icon_path}")
break
else:
logging.warning("No icon file found in any of the searched locations")
except Exception as e:
logging.error(f"Error loading icon: {str(e)}")
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.download_frame = ttk.Frame(self.notebook)
self.notebook.add(self.download_frame, text="Сваляне")
self.about_frame = ttk.Frame(self.notebook)
self.notebook.add(self.about_frame, text="За програмата")
self.main_frame = ttk.Frame(self.download_frame, padding="10")
self.main_frame.pack(fill=tk.BOTH, expand=True)
self.create_download_tab()
self.create_about_tab()
except Exception as e:
logging.error(f"Error in initialization: {str(e)}\n{traceback.format_exc()}")
messagebox.showerror("Error", f"Error initializing application: {str(e)}")
def create_download_tab(self):
try:
# URL секция
self.create_url_section()
# Дестинация секция
self.create_destination_section()
# Прогрес секция
self.create_progress_section()
# Бутони за действие
self.create_action_buttons()
# Статус
self.status_label = ttk.Label(self.main_frame,
text="Готов за сваляне",
font=('Helvetica', 10))
self.status_label.pack(pady=5)
except Exception as e:
logging.error(f"Error creating download tab: {str(e)}")
raise
def create_url_section(self):
url_frame = ttk.LabelFrame(self.main_frame, text="URL адрес", padding="5")
url_frame.pack(fill=tk.X, pady=5)
self.url_entry = ttk.Entry(url_frame, width=70)
self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
paste_button = tk.Button(url_frame,
text="Постави URL",
command=self.paste_url,
bg='#2196F3',
fg='white',
relief=tk.RAISED,
borderwidth=1,
cursor='hand2')
paste_button.pack(side=tk.RIGHT, padx=5)
def create_destination_section(self):
dest_frame = ttk.LabelFrame(self.main_frame, text="Дестинация", padding="5")
dest_frame.pack(fill=tk.X, pady=5)
self.destination_var = tk.StringVar(value=os.getcwd())
self.destination_entry = ttk.Entry(dest_frame, textvariable=self.destination_var)
self.destination_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
dest_button = tk.Button(dest_frame,
text="Избери папка",
command=self.choose_destination,
bg='#FF9800',
fg='white',
relief=tk.RAISED,
borderwidth=1,
cursor='hand2')
dest_button.pack(side=tk.RIGHT, padx=5)
def create_progress_section(self):
progress_frame = ttk.Frame(self.main_frame)
progress_frame.pack(fill=tk.X, pady=10)
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(
progress_frame,
variable=self.progress_var,
maximum=100,
mode='determinate',
length=300
)
self.progress_bar.pack(fill=tk.X)
def create_action_buttons(self):
button_frame = ttk.Frame(self.main_frame)
button_frame.pack(fill=tk.X, pady=5)
download_button = tk.Button(
button_frame,
text="Изтегли",
command=self.start_download,
bg='#4CAF50',
fg='white',
relief=tk.RAISED,
borderwidth=1,
width=15,
cursor='hand2',
font=('Helvetica', 10, 'bold')
)
download_button.pack(side=tk.LEFT, padx=5)
cancel_button = tk.Button(
button_frame,
text="Отказ",
command=self.cancel_download,
bg='#f44336',
fg='white',
relief=tk.RAISED,
borderwidth=1,
width=15,
cursor='hand2',
font=('Helvetica', 10)
)
cancel_button.pack(side=tk.LEFT, padx=5)
for button in (download_button, cancel_button):
button.bind('<Enter>', lambda e, b=button: self.on_hover(e, b))
button.bind('<Leave>', lambda e, b=button: self.on_leave(e, b))
def create_about_tab(self):
center_frame = ttk.Frame(self.about_frame)
center_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20)
title_label = ttk.Label(center_frame,
text="YouTube MP3 Downloader",
font=('Helvetica', 16, 'bold'))
title_label.pack(pady=(0,10))
version_label = ttk.Label(center_frame,
text="Версия 1.2.0",
font=('Helvetica', 10))
version_label.pack()
description = """YouTube MP3 Downloader е безплатна програма за сваляне на аудио от YouTube.
Програмата позволява лесно и бързо конвертиране на видеа в MP3 формат.
Характеристики:
- Лесен и интуитивен интерфейс
- Бързо сваляне и конвертиране
- Високо качество на звука (192kbps)
- Поддръжка на плейлисти
Важно предупреждение:
Този софтуер е създаден само с образователна цел.
"""
desc_text = tk.Text(center_frame, wrap=tk.WORD, height=10, width=50)
desc_text.insert('1.0', description)
desc_text.configure(state='disabled', border=0, font=('Helvetica', 10))
desc_text.pack(pady=20, padx=20)
website_label = ttk.Label(center_frame,
text="Посетете нашия сайт",
font=('Helvetica', 10, 'underline'),
foreground='blue',
cursor='hand2')
website_label.pack(pady=10)
website_label.bind('<Button-1>', lambda e: webbrowser.open('https://urocibg.eu/'))
copyright_label = ttk.Label(center_frame,
text="© 2024 Всички права запазени",
font=('Helvetica', 8))
copyright_label.pack(side=tk.BOTTOM, pady=5)
def validate_youtube_url(self, url):
if not url:
return False
patterns = [
r'^https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[\w-]+',
r'^https?:\/\/(?:www\.)?youtu\.be\/[\w-]+',
r'^https?:\/\/(?:www\.)?youtube\.com\/playlist\?list=[\w-]+'
]
return any(re.match(pattern, url) for pattern in patterns)
def progress_hook(self, d):
if d['status'] == 'downloading':
try:
progress = (d['downloaded_bytes'] / d['total_bytes']) * 100
self.progress_var.set(progress)
self.status_label.config(text=f"Сваляне: {progress:.1f}%")
self.root.update()
except:
pass
elif d['status'] == 'finished':
self.status_label.config(text="Конвертиране в MP3...")
self.root.update()
def paste_url(self):
try:
clipboard_text = self.root.clipboard_get()
self.url_entry.delete(0, tk.END)
self.url_entry.insert(0, clipboard_text)
except tk.TclError:
messagebox.showwarning("Предупреждение", "Няма текст в клипборда")
def choose_destination(self):
chosen_folder = filedialog.askdirectory()
if chosen_folder:
self.destination_var.set(chosen_folder)
def on_hover(self, event, button):
if button['bg'] == '#4CAF50':
button.configure(bg='#45a049')
elif button['bg'] == '#f44336':
button.configure(bg='#da190b')
elif button['bg'] == '#2196F3':
button.configure(bg='#1976D2')
elif button['bg'] == '#FF9800':
button.configure(bg='#F57C00')
def on_leave(self, event, button):
if button['bg'] in ['#45a049']:
button.configure(bg='#4CAF50')
elif button['bg'] in ['#da190b']:
button.configure(bg='#f44336')
elif button['bg'] in ['#1976D2']:
button.configure(bg='#2196F3')
elif button['bg'] in ['#F57C00']:
button.configure(bg='#FF9800')
def start_download(self):
try:
url = self.url_entry.get().strip()
logging.info(f"Starting download for URL: {url}")
if not self.validate_youtube_url(url):
logging.warning(f"Invalid URL attempted: {url}")
messagebox.showerror("Грешка", "Невалиден YouTube URL адрес")
return
destination = self.destination_var.get()
if not os.path.exists(destination):
logging.error(f"Invalid destination path: {destination}")
messagebox.showerror("Грешка", "Невалидна дестинация")
return
self.progress_var.set(0)
self.status_label.config(text="Започва сваляне...")
ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'outtmpl': os.path.join(destination, '%(title)s.%(ext)s'),
'progress_hooks': [self.progress_hook],
'no_warnings': False,
'quiet': False,
'verbose': True
}
logging.info("Starting download with yt-dlp")
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
self.progress_var.set(100)
self.status_label.config(text="Готово!")
logging.info("Download completed successfully")
messagebox.showinfo("Успех", "Файлът беше успешно свален и конвертиран в MP3 формат.")
except yt_dlp.utils.DownloadError as e:
logging.error(f"yt-dlp download error: {str(e)}\n{traceback.format_exc()}")
self.status_label.config(text="Грешка при сваляне")
messagebox.showerror("Грешка", f"Грешка при свалянето: {str(e)}")
except Exception as e:
logging.error(f"Unexpected error: {str(e)}\n{traceback.format_exc()}")
self.status_label.config(text="Грешка при сваляне")
messagebox.showerror("Грешка", f"Неочаквана грешка: {str(e)}")
finally:
self.progress_var.set(0)
def cancel_download(self):
self.status_label.config(text="Свалянето е отказано")
self.progress_var.set(0)
def run(self):
window_width = 650
window_height = 400
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
center_x = int(screen_width/2 - window_width/2)
center_y = int(screen_height/2 - window_height/2)
self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
self.root.mainloop()
if __name__ == "__main__":
root = ThemedTk(theme="breeze")
app = YouTubeDownloader(root)
app.run()
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() |