Facebook Thumbnail Downloader – DarkCorners

Date: 16/12/2025

Category: Python

  • Phần mềm hỗ trợ tải thumbnails facebook video hàng loạt.
  • Có thể điền nhiều link cùng lúc để tải xuống thumbnails facebook.
  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 : [link facebook]|[filename.jpg].
  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 [Tải tất cả ảnh].
  6. Theo dõi lịch trình xử lý.
import os
import subprocess
import time
import requests
from bs4 import BeautifulSoup
from tkinter import (
    Tk, Label, Entry, Button, Text, Scrollbar, Canvas,
    Frame, LabelFrame, filedialog, StringVar, END, RIGHT, Y, LEFT, BOTH
)
from PIL import Image, ImageTk
from io import BytesIO

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 FBThumbDownloader:
    def __init__(self, root):
        self.root = root
        self.root.title("Facebook Thumbnail Downloader - DarkCorners")
        self.root.resizable(False, False)
        center_window(self.root, 800, 600)
        self.folder_path = ""
        self.folder_var = StringVar()
        self.is_paused = False
        self.tk_img = None

        # === HEADER ===
        Label(root, text="Facebook Thumbnail Downloader - DarkCorners", font=("Helvetica", 16, "bold"), fg="blue").pack(pady=(10, 0))

        # === CONTROL PANEL ===
        top_frame = Frame(root)
        top_frame.pack(padx=10, pady=10, fill=BOTH)
        top_frame.grid_columnconfigure(0, weight=1, uniform="half")
        top_frame.grid_columnconfigure(1, weight=1, uniform="half")

        # Left control block (folder selection)
        left_panel = LabelFrame(top_frame, text="Thư Mục Lưu Ảnh", font=("Arial", 10, "bold"), bd=2, relief="groove")
        left_panel.grid(row=0, column=0, sticky="nsew", padx=5)
        self.folder_entry = Entry(left_panel, textvariable=self.folder_var, state='readonly')
        self.folder_entry.pack(padx=10, pady=2, fill=BOTH)
        folder_btn_frame = Frame(left_panel)
        folder_btn_frame.pack(pady=2)
        Button(folder_btn_frame, text="Chọn thư mục", command=self.choose_folder).pack(side=LEFT, padx=5)
        Button(folder_btn_frame, text="Mở thư mục", command=self.open_folder).pack(side=LEFT, padx=5)

        # Right control block (delay + buttons)
        right_panel = LabelFrame(top_frame, text="Điều Khiển", font=("Arial", 10, "bold"), bd=2, relief="groove")
        right_panel.grid(row=0, column=1, sticky="nsew", padx=5)
        delay_frame = Frame(right_panel)
        delay_frame.pack(pady=2, anchor='center')
        Label(delay_frame, text="Delay :").pack(side=LEFT)
        self.delay_entry = Entry(delay_frame, width=5)
        self.delay_entry.insert(0, "3")
        self.delay_entry.pack(side=LEFT, padx=5)
        Button(delay_frame, text="Tải tất cả ảnh", command=self.process_all).pack(side=LEFT, padx=5)
        self.pause_btn = Button(delay_frame, text="Tạm Dừng", command=self.toggle_pause)
        self.pause_btn.pack(side=LEFT, padx=5)
        Button(delay_frame, text="Thoát", command=self.root.quit).pack(side=LEFT, padx=5)
        btn_frame = Frame(right_panel)
        btn_frame.pack(pady=2)
        self.counter_total = Label(btn_frame, text="Tổng Số : 0", font=("Arial", 9, "bold"), fg="red")
        self.counter_total.pack(side=LEFT, padx=5)
        self.counter_success = Label(btn_frame, text="Đã Tải : 0", font=("Arial", 9, "bold"), fg="green")
        self.counter_success.pack(side=LEFT, padx=5)
        self.counter_pending = Label(btn_frame, text="Đợi Tải : 0", font=("Arial", 9, "bold"), fg="blue")
        self.counter_pending.pack(side=LEFT, padx=5)
        self.counter_error = Label(btn_frame, text="Bị Lỗi : 0", font=("Arial", 9, "bold"), fg="black")
        self.counter_error.pack(side=LEFT, padx=5)
        # === FORM 2 ===
        form2_frame = LabelFrame(root, text="Danh Sách Liên Kết Facebook Videos - FacebookURL|Filename.jpg", font=("Arial", 10, "bold"), bd=2, relief="groove")
        form2_frame.pack(padx=10, pady=5, fill=BOTH)

        self.textbox = Text(form2_frame, width=100, height=8)
        self.textbox.pack(padx=10, pady=5, fill=BOTH)

        # === FORM 3 + 4 ===
        bottom_frame = Frame(root)
        bottom_frame.pack(pady=10, fill=BOTH, expand=True)

        # FORM 3: Canvas hiển thị ảnh
        left_frame = LabelFrame(bottom_frame, text="Hình Ảnh Mới Nhất", font=("Arial", 10, "bold"), bd=2, relief="groove")
        left_frame.pack(side=LEFT, padx=10, fill=BOTH, expand=True)
        self.canvas = Canvas(left_frame, width=400, height=250, bg='white')
        self.canvas.pack(expand=True)
        self.canvas.create_text(200, 125, text="Chưa có ảnh", fill="gray")

        # FORM 4: Log trạng thái
        right_frame = LabelFrame(bottom_frame, text="Trạng Thái Tải Xuống", font=("Arial", 10, "bold"), bd=2, relief="groove")
        right_frame.pack(side=LEFT, padx=10, fill=BOTH, expand=True)
        self.logbox = Text(right_frame, width=50, height=14, fg="green", wrap='word')
        self.logbox.pack(side="left", fill=BOTH, expand=True)
        self.scrollbar = Scrollbar(right_frame, command=self.logbox.yview)
        self.scrollbar.pack(side="right", fill="y")
        self.logbox.config(yscrollcommand=self.scrollbar.set)

    def log(self, message):
        self.logbox.insert(END, message + '\n')
        self.logbox.see(END)

    def choose_folder(self):
        self.folder_path = filedialog.askdirectory()
        if self.folder_path:
            self.folder_var.set(self.folder_path)
            self.log(f"📁 Đã chọn thư mục: {self.folder_path}")

    def open_folder(self):
        if self.folder_path and os.path.exists(self.folder_path):
            subprocess.Popen(f'explorer "{self.folder_path}"')
        else:
            self.log("⚠️ Chưa chọn thư mục hoặc thư mục không tồn tại!")

    def toggle_pause(self):
        self.is_paused = not self.is_paused
        if self.is_paused:
            self.pause_btn.config(text="▶️ Tiếp Tục")
            self.log("⏸️ Đã tạm dừng.")
        else:
            self.pause_btn.config(text="⏸️ Tạm Dừng")
            self.log("✅ Tiếp tục.")

    def process_all(self):
        lines = self.textbox.get("1.0", END).strip().split("\n")
        total = len([l for l in lines if '|' in l])
        success = 0
        error = 0
        pending = total

        self.counter_total.config(text=f"Tổng Số : {total}")
        self.counter_success.config(text=f"Đã Tải : {success}")
        self.counter_pending.config(text=f"Đợi Tải : {pending}")
        self.counter_error.config(text=f"Bị Lỗi : {error}")

        try:
            delay = int(self.delay_entry.get())
            if delay < 0 or delay > 60:
                self.log("⚠️ Thời gian chờ phải từ 1 đến 60 giây. Dùng mặc định 3s.")
                delay = 3
        except:
            delay = 3

        for i, line in enumerate(lines, start=1):
            while self.is_paused:
                self.log("⏸️ Đang tạm dừng... Đợi tiếp tục.")
                self.root.update()
                time.sleep(1)

            if "|" not in line:
                self.log(f"⛔ Dòng {i}: Sai định dạng (thiếu dấu |): {line}")
                continue

            url, filename = line.strip().split("|", 1)
            success_download = self.download_and_save(url.strip(), filename.strip())
            if success_download:
                success += 1
            else:
                error += 1
            pending = total - (success + error)

            self.counter_success.config(text=f"Đã Tải : {success}")
            self.counter_error.config(text=f"Bị Lỗi : {error}")
            self.counter_pending.config(text=f"Đợi Tải : {pending}")

            # Đếm ngược
            for s in range(delay, 0, -1):
                self.log(f"⏳ Chờ {s} giây trước khi xử lý dòng tiếp theo...")
                self.root.update()
                time.sleep(1)

    def download_and_save(self, url, filename):
        success = False
        try:
            headers = {'User-Agent': 'Mozilla/5.0'}
            r = requests.get(url, headers=headers, timeout=10)
            soup = BeautifulSoup(r.text, 'html.parser')
            meta = soup.find("meta", property="og:image")
            if not meta:
                self.log(f"❌ Không tìm thấy thumbnail cho: {url}")
                return success

            thumb_url = meta["content"]
            img_data = requests.get(thumb_url).content
            save_path = os.path.join(self.folder_path, filename)
            with open(save_path, 'wb') as f:
                f.write(img_data)

            self.log(f"✅ Đã lưu ảnh: {filename}")
            success = True

            img = Image.open(BytesIO(img_data))
            img.thumbnail((400, 250))
            self.tk_img = ImageTk.PhotoImage(img)
            self.canvas.delete("all")
            self.canvas.create_image(200, 125, anchor='center', image=self.tk_img)

        except Exception as e:
            self.log(f"⚠️ Lỗi khi xử lý {url}: {e}")
        return success

if __name__ == "__main__":
    root = Tk()
    app = FBThumbDownloader(root)
    root.mainloop()
pyinstaller --noconsole --onefile --windowed --add-data "2-FacebookThumbnailDownloader-icon.ico;." --icon=2-FacebookThumbnailDownloader-icon.ico 2-FacebookThumbnailDownloader-Source.py

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