Youtube Thumbnails Downloader – DarkCorners

Date: 16/12/2025

Category: Python

  • Phần mềm hỗ trợ tải thumbnails youtube hàng loạt.
  • Có thể điền nhiều link cùng lúc để tải xuống thumbnails youtube.
  1. Khởi động phần mềm.
  2. Chọn thư mục chứa tệp được tải xuống.
  3. Điền thông tin theo cấu trúc : [filename]|[link youtube].
  4. Phần mềm sẽ tải xuống và lưu theo tên mà bạn đặt.
  5. Nhấn nút [Bắt Đầu Tải].
  6. Theo dõi lịch trình xử lý.
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()
pyinstaller --noconsole --onefile --windowed --add-data "1-YoutubeThumbsDownloader-icon.ico;." --icon=1-YoutubeThumbsDownloader-icon.ico 1-YoutubeThumbsDownloader-Source.py

Để lại một bình luận