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()