import os
import sys
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, scrolledtext
from threading import Thread
import queue
import urllib.parse
import requests
def center_window(root, width=800, height=600):
# Lấy kích thước màn hình
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# Tính vị trí bắt đầu trục ngang và trục đứng
aaaxxx = (screen_width // 2) - (width // 2)
bbbyyy = (screen_height // 2) - (height // 2) - (40)
# Đặt geometry
root.geometry(f"{width}x{height}+{aaaxxx}+{bbbyyy}")
class YoutubeDownloaderApp:
def __init__(self, root):
self.root = root
self.root.title("Youtube Thumbnails Downloader - DarkCorners")
self.root.resizable(False, False)
center_window(self.root, 800, 600)
self.thumb_folder = tk.StringVar()
self.delay_min = tk.IntVar(value=5)
self.delay_max = tk.IntVar(value=15)
self.status_message = tk.StringVar(value="")
self.stop_download = False
self.log_queue = queue.Queue()
self.count_total = 0
self.count_done = 0
self.count_error = 0
self.create_widgets()
def create_widgets(self):
tk.Label(self.root, text="Youtube Thumbnails Downloader", font=("Arial", 18, "bold")).pack(pady=10)
folder_frame = tk.LabelFrame(self.root, text="CHỌN NƠI TẢI XUỐNG", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n', padx=10, pady=10)
folder_frame.pack(fill='x', padx=10, pady=5)
folder_row = tk.Frame(folder_frame)
folder_row.pack(fill='x')
col = tk.Frame(folder_row)
col.pack(side='left', expand=True, fill='x', padx=5)
inner = tk.Frame(col)
inner.pack(fill='x')
tk.Label(inner, text="Thư Mục Thumbs:", width=14, anchor='w').pack(side='left')
tk.Entry(inner, textvariable=self.thumb_folder).pack(side='left', fill='x', expand=True, padx=5)
tk.Button(inner, text="Chọn", command=self.select_thumb_folder).pack(side='left')
video_note_row = tk.Frame(self.root)
video_note_row.pack(fill='x', padx=10, pady=5)
video_note_row.columnconfigure(0, weight=3)
video_note_row.columnconfigure(1, weight=2)
video_input_frame = tk.LabelFrame(video_note_row, text="DANH SÁCH VIDEO", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n')
video_input_frame.pack(side='left')
video_input_frame.config(width=514, height=200)
video_input_frame.pack_propagate(False)
self.text_links = scrolledtext.ScrolledText(video_input_frame, height=8)
self.text_links.pack(fill='both', expand=True)
note_frame_container = tk.Frame(video_note_row, width=286, height=200)
note_frame_container.pack(side='left', padx=(10, 0))
note_frame_container.pack_propagate(False)
note_frame = tk.LabelFrame(note_frame_container, text="LƯU Ý SỬ DỤNG", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n')
note_frame.pack(fill='both', expand=True)
tk.Label(note_frame, text="- Mỗi dòng: filename|link.", anchor='w', justify='left', wraplength=240).pack(anchor='w', padx=10, pady=2)
tk.Label(note_frame, text="- Tải thumbnail JPG chất lượng cao từ YouTube.", anchor='w', justify='left', wraplength=240).pack(anchor='w', padx=10, pady=2)
main_status_row = tk.Frame(self.root)
main_status_row.pack(fill='x', padx=10, pady=10)
left_frame = tk.LabelFrame(main_status_row, text="THỐNG KÊ & ĐIỀU KHIỂN", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n')
left_frame.pack(side='left', fill='both', expand=True, padx=5, pady=5)
stats_row = tk.Frame(left_frame)
stats_row.pack(pady=3)
self.total_label = tk.Label(stats_row, text="[Tổng Số : 0]", font=('Arial', 10, 'bold'), fg='red')
self.total_label.pack(side='left', padx=5)
self.done_label = tk.Label(stats_row, text="[Đã Tải: 0]", font=('Arial', 10, 'bold'), fg='green')
self.done_label.pack(side='left', padx=5)
self.waiting_label = tk.Label(stats_row, text="[Đang Đợi: 0]", font=('Arial', 10, 'bold'), fg='blue')
self.waiting_label.pack(side='left', padx=5)
self.error_label = tk.Label(stats_row, text="[Bị Lỗi : 0]", font=('Arial', 10, 'bold'), fg='black')
self.error_label.pack(side='left', padx=5)
options_row = tk.Frame(left_frame)
options_row.pack(pady=3)
tk.Label(options_row, text="Thời gian delay :").pack(side='left')
tk.Entry(options_row, textvariable=self.delay_min, width=5).pack(side='left', padx=2)
tk.Label(options_row, text="đến").pack(side='left')
tk.Entry(options_row, textvariable=self.delay_max, width=5).pack(side='left', padx=2)
controls_row = tk.Frame(left_frame)
controls_row.pack(pady=3)
tk.Button(controls_row, text="Bắt Đầu Tải", command=self.start_download).pack(side='left', padx=10)
tk.Button(controls_row, text="Dừng Lại", command=self.stop_download_func).pack(side='left', padx=10)
tk.Button(controls_row, text="Thoát", command=self.quit_app).pack(side='left', padx=10)
right_frame = tk.LabelFrame(main_status_row, text="TIẾN TRÌNH", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n')
right_frame.pack(side='right', fill='both', expand=True, padx=5, pady=5)
status_row = tk.Frame(right_frame)
status_row.pack(fill='x', pady=5, padx=10)
tk.Label(status_row, text="Trạng Thái:").pack(side='left')
tk.Label(status_row, textvariable=self.status_message, fg="blue").pack(side='left')
log_frame = tk.LabelFrame(self.root, text="LOGS", font=("Arial", 10, "bold"), bd=2, relief='groove', labelanchor='n')
log_frame.pack(fill='both', padx=10, pady=5, expand=True)
self.log_text = scrolledtext.ScrolledText(log_frame, height=10)
self.log_text.pack(fill='both', expand=True)
self.root.after(100, self.process_log_queue)
def update_stats_labels(self):
waiting = self.count_total - self.count_done - self.count_error
self.total_label.config(text=f"[Tổng Số: {self.count_total}]")
self.done_label.config(text=f"[Đã Tải: {self.count_done}]")
self.waiting_label.config(text=f"[Đang Đợi: {waiting}]")
self.error_label.config(text=f"[Bị Lỗi: {self.count_error}]")
def select_thumb_folder(self):
folder = filedialog.askdirectory()
if folder:
self.thumb_folder.set(folder)
def log(self, message):
self.log_queue.put(message)
def process_log_queue(self):
while not self.log_queue.empty():
msg = self.log_queue.get()
self.log_text.insert(tk.END, msg + "\n")
self.log_text.see(tk.END)
self.root.after(100, self.process_log_queue)
def start_download(self):
self.stop_download = False
self.status_message.set("")
self.link_list = [line.strip() for line in self.text_links.get("1.0", tk.END).strip().splitlines() if '|' in line]
if not self.link_list:
messagebox.showerror("Lỗi", "Danh sách không hợp lệ.")
return
Thread(target=self.download_all).start()
def stop_download_func(self):
self.stop_download = True
self.log("⛔ Đã yêu cầu dừng quá trình tải.")
def extract_video_id(self, url):
query = urllib.parse.urlparse(url)
if query.hostname in ['www.youtube.com', 'youtube.com']:
qs = urllib.parse.parse_qs(query.query)
return qs.get('v', [None])[0]
elif query.hostname == 'youtu.be':
return query.path[1:]
return None
def download_file(self, url, output_path):
try:
r = requests.get(url, stream=True)
r.raise_for_status()
with open(output_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if self.stop_download:
return False
f.write(chunk)
return True
except Exception as e:
self.log(f"❌ Lỗi tải thumbnail: {e}")
return False
def download_all(self):
self.count_total = len(self.link_list)
self.count_done = 0
self.count_error = 0
self.update_stats_labels()
for idx, line in enumerate(self.link_list):
if self.stop_download:
self.log("⛔ Đã dừng tải.")
break
try:
filename, url = map(str.strip, line.split("|", 1))
self.log(f"▶ Đang tải thumbnail cho: {filename}")
self.status_message.set("Đang tải thumbnail...")
video_id = self.extract_video_id(url)
if video_id:
thumb_base = os.path.join(self.thumb_folder.get(), filename)
thumb_url = f"https://i.ytimg.com/vi/{video_id}/maxresdefault.jpg"
self.log(f"🖼️ Tải thumbnail jpg: {thumb_url}")
self.download_file(thumb_url, thumb_base + ".jpg")
self.count_done += 1
else:
self.log(f"❌ Không xác định được ID video: {url}")
self.count_error += 1
self.update_stats_labels()
except Exception as e:
self.count_error += 1
self.update_stats_labels()
self.status_message.set("❌ Lỗi trong quá trình tải")
self.log(f"❌ Lỗi với video \"{filename}\": {e}")
self.status_message.set("🎉 Đã hoàn tất toàn bộ.")
self.update_stats_labels()
def quit_app(self):
self.stop_download_func()
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = YoutubeDownloaderApp(root)
root.mainloop()