Tiktok Video Downloader – DarkCorners

Date: 17/12/2025

Category: Python

  • Phần mềm hỗ trợ tải xuống video từ tiktok hàng loạt.
  • Phần mềm sẽ tải xuống video từ tiktok.
  1. Khởi động phần mềm.
  2. Chọn thư mục chứa tệp được xuất ra.
  3. Điền danh sách liên kết tiktok cần tải xuống.
  4. Chọn chất lượng video sẽ tải xuống. Hỗ trợ 360, 480, 720, 1080.
  5. Nhấn nút [Tải Xuống].
  6. Theo dõi lịch trình xử lý.
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, scrolledtext
import threading
import os
import re
import yt_dlp

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 TikTokDownloaderApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Tiktok Video Downloader - DarkCorners")
        self.root.resizable(False, False)
        center_window(self.root, 800, 600)
        self.folder_path = tk.StringVar()
        self.quality = tk.StringVar(value="720p")
        self.links = []
        self.downloading = False
        self.setup_ui()

    def setup_ui(self):
        # Hàng 1: Tên phần mềm
        title = tk.Label(self.root, text="Tiktok Video Downloader - DarkCorners", font=("Arial", 18, "bold"))
        title.pack(pady=10)

        # Hàng 2: Cấu Hình và Điều Khiển chia 2 phần
        frame2 = tk.Frame(self.root)
        frame2.pack(pady=5)
        # Bên Trái: Cấu Hình (500px)
        config_frame = tk.LabelFrame(frame2, text="Cấu Hình", font=("Arial", 10, "bold"), width=500, height=100)
        config_frame.pack(side=tk.LEFT, padx=(10, 5))
        config_frame.pack_propagate(False)
        config_row1 = tk.Frame(config_frame)
        config_row1.pack(fill='x', padx=5, pady=2)
        tk.Entry(config_row1, textvariable=self.folder_path, state='readonly').pack(side=tk.LEFT, padx=(5, 2), fill='x', expand=True)
        tk.Button(config_row1, text="Chọn Thư Mục", command=self.choose_folder).pack(side=tk.LEFT, padx=(2, 5))
        tk.Label(config_frame, text="1. Chọn thư mục chứa videos tải xuống !", font=("Arial", 9, "bold"), fg="red").pack(anchor="w", padx=5, pady=(0, 2))
        tk.Label(config_frame, text="2. Danh sách liên kết tải xuống hỗ trợ nhiều dòng !", font=("Arial", 9, "bold"), fg="red").pack(anchor="w", padx=5, pady=(0, 5))
        # Bên Phải: Điều Khiển (300px)
        control_frame = tk.LabelFrame(frame2, text="Điều Khiển", font=("Arial", 10, "bold"), width=300, height=100)
        control_frame.pack(side=tk.LEFT, padx=(5, 10))
        control_frame.pack_propagate(False)
        control_row1 = tk.Frame(control_frame)
        control_row1.pack(anchor="w", pady=2)
        tk.Label(control_row1, text="Chọn chất lượng video: ").pack(side=tk.LEFT, padx=(5, 2))
        quality_menu = ttk.Combobox(control_row1, textvariable=self.quality, values=["480p", "720p", "1080p"], width=10, state="readonly")
        quality_menu.pack(side=tk.LEFT, padx=(2, 5))
        # Đường phân cách giữa chọn chất lượng và nút điều khiển
        separator_ctrl = ttk.Separator(control_frame, orient='horizontal')
        separator_ctrl.pack(fill='x', padx=5, pady=(5, 5))
        control_row2 = tk.Frame(control_frame)
        control_row2.pack(pady=5)
        # Căn giữa nội dung bằng cách thêm 1 frame phụ bọc các nút
        buttons_container = tk.Frame(control_row2)
        buttons_container.pack()
        tk.Button(buttons_container, text="Tải Xuống", command=self.start_download).pack(side=tk.LEFT, padx=5)
        tk.Button(buttons_container, text="Tạm Dừng", command=self.pause_download).pack(side=tk.LEFT, padx=5)
        tk.Button(buttons_container, text="Thoát", command=self.root.quit).pack(side=tk.LEFT, padx=5)

        # Hàng 3 : Danh sách liên kết và Tiến trình xử lý
        frame34 = tk.Frame(self.root)
        frame34.pack(pady=5)
        # Bên trái: Danh Sách Liên Kết
        links_frame = tk.LabelFrame(frame34, text="Danh Sách Liên Kết", font=("Arial", 10, "bold"), width=500, height=200)
        links_frame.pack(side=tk.LEFT, padx=(10, 5))
        links_frame.pack_propagate(False)
        self.text_links = tk.Text(links_frame, height=10)
        self.text_links.pack(fill='both', expand=True, padx=5, pady=5)
        # Bên phải: Tiến Trình Xử Lý
        progress_frame = tk.LabelFrame(frame34, text="Tiến Trình Xử Lý", font=("Arial", 10, "bold"), width=300, height=200)
        progress_frame.pack(side=tk.LEFT, padx=(5, 10))
        progress_frame.pack_propagate(False)
        # Dòng 1: Tiến trình hiện tại
        row1 = tk.Frame(progress_frame)
        row1.pack(fill='x', pady=(10, 2), padx=5)
        tk.Label(row1, text="Hiện Tại:", width=10).pack(side=tk.LEFT)
        self.progress_current = ttk.Progressbar(row1, orient="horizontal", mode="determinate")
        self.progress_current.pack(side=tk.LEFT, fill='x', expand=True)
        # Dòng 2: Tiến trình tổng
        row2 = tk.Frame(progress_frame)
        row2.pack(fill='x', pady=2, padx=5)
        tk.Label(row2, text="Toàn Bộ:", width=10).pack(side=tk.LEFT)
        self.progress_total = ttk.Progressbar(row2, orient="horizontal", mode="determinate")
        self.progress_total.pack(side=tk.LEFT, fill='x', expand=True)
        # Đường phân cách
        separator = ttk.Separator(progress_frame, orient='horizontal')
        separator.pack(fill='x', padx=5, pady=(5, 5))
        # Dòng 3-6: Các bộ đếm
        self.total_count_var = tk.StringVar(value="0")
        self.success_count_var = tk.StringVar(value="0")
        self.pending_count_var = tk.StringVar(value="0")
        self.failed_count_var = tk.StringVar(value="0")
        counters = [
            ("Toàn Bộ:", self.total_count_var, "red"),
            ("Đã Tải:", self.success_count_var, "green"),
            ("Đợi Tải:", self.pending_count_var, "blue"),
            ("Bị Lỗi:", self.failed_count_var, "black")
        ]
        for label_text, var, color in counters:
            row = tk.Frame(progress_frame)
            row.pack(anchor='w', padx=5)
            tk.Label(row, text=label_text, font=("Arial", 9, "bold"), fg=color, width=10).pack(side=tk.LEFT)
            tk.Label(row, textvariable=var, font=("Arial", 9, "bold"), fg=color).pack(side=tk.LEFT)

        # Hàng 4: Logs
        logs_frame = tk.LabelFrame(self.root, text="Lịch Trình Xử Lý", font=("Arial", 10, "bold"))
        logs_frame.pack(fill='both', expand=True, padx=10, pady=5)
        self.log_box = scrolledtext.ScrolledText(logs_frame, height=15, width=95, state='disabled')
        self.log_box.pack(fill='both', expand=True, padx=5, pady=5)

    def choose_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.folder_path.set(folder)

    def log(self, message):
        self.log_box.configure(state='normal')
        self.log_box.insert(tk.END, message + "\n")
        self.log_box.see(tk.END)
        self.log_box.configure(state='disabled')

    def pause_download(self):
        self.downloading = False
        self.log("⏸️ Tải xuống đã bị tạm dừng.")

    def start_download(self):
        self.total_count_var.set("0")
        self.success_count_var.set("0")
        self.pending_count_var.set("0")
        self.failed_count_var.set("0")
        if self.downloading:
            messagebox.showinfo("Đang tải", "Đang trong quá trình tải video.")
            return
        self.links = [line.strip() for line in self.text_links.get("1.0", tk.END).splitlines() if line.strip()]
        if not self.links:
            messagebox.showwarning("Thiếu dữ liệu", "Vui lòng nhập ít nhất 1 link video TikTok.")
            return
        if not self.folder_path.get():
            messagebox.showwarning("Thiếu thư mục", "Vui lòng chọn thư mục lưu video.")
            return
        self.progress_current['value'] = 0
        self.progress_total['value'] = 0
        self.progress_total['maximum'] = len(self.links)
        self.total_count_var.set(str(len(self.links)))
        self.pending_count_var.set(str(len(self.links)))
        self.downloading = True
        threading.Thread(target=self.download_videos, daemon=True).start()

    def sanitize_filename(self, name):
        """Loại bỏ ký tự không hợp lệ khỏi tên file"""
        return re.sub(r'[\\/*?:"<>|]', "", name).strip()

    def download_videos(self):
        for idx, link in enumerate(self.links):
            self.pending_count_var.set(str(int(self.pending_count_var.get()) - 1))
            if not self.downloading:
                break
            self.log(f"🔗 Đang tải video {idx+1}/{len(self.links)}: {link}")
            try:
                self.download_video(link)
                self.log(f"✅ Tải thành công video {idx+1}")
                self.success_count_var.set(str(int(self.success_count_var.get()) + 1))
            except Exception as e:
                self.log(f"❌ Lỗi video {idx+1}: {e}")
                self.failed_count_var.set(str(int(self.failed_count_var.get()) + 1))
            self.progress_total['value'] += 1
        self.log("🎉 Hoàn tất tải video.")
        self.downloading = False

    def download_video(self, link):
        quality_pref = self.quality.get()
        def progress_hook(d):
            if d['status'] == 'downloading':
                total = d.get('total_bytes', 1)
                downloaded = d.get('downloaded_bytes', 0)
                percent = int(downloaded * 100 / total)
                self.progress_current['value'] = percent
            elif d['status'] == 'finished':
                self.progress_current['value'] = 100
        # Lấy thông tin metadata trước
        with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
            info = ydl.extract_info(link, download=False)
            title = self.sanitize_filename(info.get('title', f"video_{info.get('id')}"))
        output_template = os.path.join(self.folder_path.get(), f"{title}.%(ext)s")
        ydl_opts = {
            'outtmpl': output_template,
            'progress_hooks': [progress_hook],
            'quiet': True,
            'merge_output_format': 'mp4',
            'format': f'bestvideo[height<={quality_pref[:-1]}]+bestaudio/best[height<={quality_pref[:-1]}]/best',
        }
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            ydl.download([link])

# Chạy ứng dụng
if __name__ == "__main__":
    root = tk.Tk()
    app = TikTokDownloaderApp(root)
    root.mainloop()
pyinstaller --noconsole --onefile --windowed --add-data "3-TiktokVideoDownloader-icon.ico;." --icon=3-TiktokVideoDownloader-icon.ico 3-TiktokVideoDownloader-Source.py

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