383 lines
16 KiB
Python
383 lines
16 KiB
Python
import tkinter as tk
|
||
from tkinter import ttk, filedialog, scrolledtext, messagebox
|
||
import subprocess
|
||
import threading
|
||
import os
|
||
import re
|
||
|
||
PYTHON_PATH = r"C:\Program Files\Python311\python.exe"
|
||
COOKIE_FILE = r"C:\Users\Administrator\Desktop\TikTokDownload-main\douyin_cookie.txt"
|
||
|
||
class DouyinDownloaderUI:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("抖音去水印下载器 v3.0 (自动Cookie)")
|
||
self.root.geometry("800x700")
|
||
self.root.resizable(True, True)
|
||
|
||
self.download_dir = os.path.join(os.path.expanduser("~"), "Downloads", "DouyinDownloads")
|
||
os.makedirs(self.download_dir, exist_ok=True)
|
||
|
||
self.is_downloading = False
|
||
self.cookie = None
|
||
|
||
self.setup_ui()
|
||
self.auto_load_cookie()
|
||
|
||
def auto_load_cookie(self):
|
||
"""启动时自动加载Cookie"""
|
||
if os.path.exists(COOKIE_FILE):
|
||
try:
|
||
with open(COOKIE_FILE, 'r', encoding='utf-8') as f:
|
||
self.cookie = f.read().strip()
|
||
if self.cookie and len(self.cookie) > 100:
|
||
self.update_cookie_status(True)
|
||
self.log("✓ 已自动加载Cookie", "SUCCESS")
|
||
return
|
||
except:
|
||
pass
|
||
|
||
self.update_cookie_status(False)
|
||
self.log("ℹ 未检测到Cookie,首次下载时将自动获取", "INFO")
|
||
|
||
def update_cookie_status(self, has_cookie):
|
||
"""更新Cookie状态指示"""
|
||
if has_cookie:
|
||
self.cookie_status.config(text="● Cookie: 有效", foreground="green")
|
||
self.cookie_entry.delete(0, tk.END)
|
||
self.cookie_entry.insert(0, "[已自动获取Cookie]")
|
||
else:
|
||
self.cookie_status.config(text="● Cookie: 未获取", foreground="gray")
|
||
|
||
def setup_ui(self):
|
||
style = ttk.Style()
|
||
style.theme_use('clam')
|
||
|
||
main_frame = ttk.Frame(self.root, padding="15")
|
||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# Title
|
||
title_label = ttk.Label(main_frame, text="抖音/TikTok 去水印下载器", font=('Microsoft YaHei', 18, 'bold'))
|
||
title_label.pack(pady=(0, 5))
|
||
|
||
subtitle = ttk.Label(main_frame, text="自动Cookie | 无需登录 | 一键下载", font=('Microsoft YaHei', 10), foreground="gray")
|
||
subtitle.pack(pady=(0, 10))
|
||
|
||
# Cookie Status Frame
|
||
cookie_frame = ttk.LabelFrame(main_frame, text="Cookie状态", padding="10")
|
||
cookie_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
self.cookie_status = ttk.Label(cookie_frame, text="● Cookie: 检测中...", font=('Microsoft YaHei', 10, 'bold'))
|
||
self.cookie_status.pack(side=tk.LEFT, padx=5)
|
||
|
||
ttk.Button(cookie_frame, text="自动获取Cookie", command=self.auto_get_cookie_ui, width=15).pack(side=tk.LEFT, padx=10)
|
||
|
||
ttk.Label(cookie_frame, text="(首次下载时会自动获取,无需手动操作)",
|
||
font=('Microsoft YaHei', 8), foreground="gray").pack(side=tk.LEFT, padx=5)
|
||
|
||
# Hidden Cookie Entry (for compatibility)
|
||
self.cookie_entry = ttk.Entry(cookie_frame, width=20, state='disabled')
|
||
|
||
# URL Frame
|
||
input_frame = ttk.LabelFrame(main_frame, text="下载设置", padding="10")
|
||
input_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
ttk.Label(input_frame, text="分享链接:", font=('Microsoft YaHei', 10)).grid(row=0, column=0, sticky=tk.W, pady=5)
|
||
self.url_entry = ttk.Entry(input_frame, width=80, font=('Microsoft YaHei', 10))
|
||
self.url_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
|
||
self.url_entry.bind('<Control-v>', self.paste_clipboard)
|
||
|
||
ttk.Label(input_frame, text="下载模式:", font=('Microsoft YaHei', 10)).grid(row=1, column=0, sticky=tk.W, pady=5)
|
||
self.mode_var = tk.StringVar(value="one")
|
||
mode_frame = ttk.Frame(input_frame)
|
||
mode_frame.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
|
||
|
||
modes = [
|
||
("单个作品", "one"),
|
||
("用户主页", "post"),
|
||
("用户喜欢", "like"),
|
||
("用户收藏", "collects"),
|
||
("直播", "live"),
|
||
]
|
||
|
||
for i, (text, value) in enumerate(modes):
|
||
rb = ttk.Radiobutton(mode_frame, text=text, value=value, variable=self.mode_var)
|
||
rb.pack(side=tk.LEFT, padx=8)
|
||
|
||
ttk.Label(input_frame, text="保存目录:", font=('Microsoft YaHei', 10)).grid(row=2, column=0, sticky=tk.W, pady=5)
|
||
dir_frame = ttk.Frame(input_frame)
|
||
dir_frame.grid(row=2, column=1, sticky=tk.EW, padx=5, pady=5)
|
||
|
||
self.dir_entry = ttk.Entry(dir_frame, textvariable=tk.StringVar(value=self.download_dir), font=('Microsoft YaHei', 10))
|
||
self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||
|
||
ttk.Button(dir_frame, text="浏览", command=self.browse_dir, width=8).pack(side=tk.LEFT, padx=(5, 0))
|
||
|
||
input_frame.columnconfigure(1, weight=1)
|
||
|
||
# Options Frame
|
||
options_frame = ttk.LabelFrame(main_frame, text="选项", padding="10")
|
||
options_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
self.download_music = tk.BooleanVar(value=False)
|
||
self.download_cover = tk.BooleanVar(value=False)
|
||
|
||
ttk.Checkbutton(options_frame, text="下载原声", variable=self.download_music).pack(side=tk.LEFT, padx=10)
|
||
ttk.Checkbutton(options_frame, text="下载封面", variable=self.download_cover).pack(side=tk.LEFT, padx=10)
|
||
|
||
ttk.Label(options_frame, text="作品数量:").pack(side=tk.LEFT, padx=(20, 5))
|
||
self.limit_entry = ttk.Entry(options_frame, width=8)
|
||
self.limit_entry.insert(0, "0")
|
||
self.limit_entry.pack(side=tk.LEFT)
|
||
ttk.Label(options_frame, text="(0=全部)").pack(side=tk.LEFT, padx=5)
|
||
|
||
# Buttons
|
||
btn_frame = ttk.Frame(main_frame)
|
||
btn_frame.pack(fill=tk.X, pady=10)
|
||
|
||
self.download_btn = ttk.Button(btn_frame, text="开始下载", command=self.start_download)
|
||
self.download_btn.pack(side=tk.LEFT, padx=5, ipadx=20, ipady=5)
|
||
|
||
self.stop_btn = ttk.Button(btn_frame, text="停止", command=self.stop_download, state=tk.DISABLED)
|
||
self.stop_btn.pack(side=tk.LEFT, padx=5, ipadx=20, ipady=5)
|
||
|
||
ttk.Button(btn_frame, text="打开目录", command=self.open_download_dir).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# Log Frame
|
||
log_frame = ttk.LabelFrame(main_frame, text="下载日志", padding="5")
|
||
log_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
self.log_text = scrolledtext.ScrolledText(log_frame, height=15, font=('Consolas', 9),
|
||
bg='#1e1e1e', fg='#00ff00', insertbackground='white')
|
||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# Progress
|
||
self.progress_var = tk.DoubleVar()
|
||
self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
|
||
self.progress_bar.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
self.status_label = ttk.Label(main_frame, text="就绪", font=('Microsoft YaHei', 9))
|
||
self.status_label.pack(pady=5)
|
||
|
||
def auto_get_cookie_ui(self):
|
||
"""UI线程:自动获取Cookie"""
|
||
self.log("正在自动获取Cookie...", "INFO")
|
||
self.status_label.config(text="正在获取Cookie...")
|
||
self.download_btn.config(state=tk.DISABLED)
|
||
|
||
thread = threading.Thread(target=self.auto_get_cookie_thread)
|
||
thread.daemon = True
|
||
thread.start()
|
||
|
||
def auto_get_cookie_thread(self):
|
||
"""后台线程:自动获取Cookie"""
|
||
try:
|
||
from auto_cookie import get_douyin_cookie_auto
|
||
|
||
# 强制刷新,使用有界面模式(更稳定)
|
||
cookie = get_douyin_cookie_auto(force_refresh=True, headless=False)
|
||
|
||
if cookie and len(cookie) > 100:
|
||
self.cookie = cookie
|
||
self.root.after(0, lambda: self.update_cookie_status(True))
|
||
self.root.after(0, lambda: self.log("✓ Cookie获取成功!", "SUCCESS"))
|
||
self.root.after(0, lambda: self.status_label.config(text="Cookie已就绪"))
|
||
else:
|
||
self.root.after(0, lambda: self.log("✗ Cookie获取失败", "ERROR"))
|
||
self.root.after(0, lambda: self.status_label.config(text="Cookie获取失败"))
|
||
|
||
except Exception as e:
|
||
self.root.after(0, lambda: self.log(f"✗ Cookie获取失败: {str(e)}", "ERROR"))
|
||
self.root.after(0, lambda: self.status_label.config(text="Cookie获取失败"))
|
||
finally:
|
||
self.root.after(0, lambda: self.download_btn.config(state=tk.NORMAL))
|
||
|
||
def paste_clipboard(self, event=None):
|
||
try:
|
||
clipboard = self.root.clipboard_get()
|
||
self.url_entry.delete(0, tk.END)
|
||
self.url_entry.insert(0, clipboard)
|
||
except:
|
||
pass
|
||
return "break"
|
||
|
||
def browse_dir(self):
|
||
dir_path = filedialog.askdirectory(initialdir=self.download_dir)
|
||
if dir_path:
|
||
self.download_dir = dir_path
|
||
self.dir_entry.delete(0, tk.END)
|
||
self.dir_entry.insert(0, dir_path)
|
||
|
||
def open_download_dir(self):
|
||
os.startfile(self.download_dir)
|
||
|
||
def clear_log(self):
|
||
self.log_text.delete(1.0, tk.END)
|
||
|
||
def log(self, message, level="INFO"):
|
||
self.log_text.insert(tk.END, f"[{level}] {message}\n")
|
||
self.log_text.see(tk.END)
|
||
self.root.update_idletasks()
|
||
|
||
def extract_url(self, text):
|
||
match = re.search(r'https?://[^\s<>"{}|\\^`\[\]]+', text)
|
||
if match:
|
||
return match.group(0)
|
||
return text
|
||
|
||
def ensure_cookie(self):
|
||
"""确保有Cookie,如果没有则自动获取"""
|
||
if self.cookie and len(self.cookie) > 100:
|
||
return True
|
||
|
||
self.log("检测到无Cookie,正在自动获取...", "INFO")
|
||
|
||
try:
|
||
from auto_cookie import get_douyin_cookie_auto
|
||
|
||
# 自动获取,使用无头模式(不打扰用户)
|
||
cookie = get_douyin_cookie_auto(force_refresh=False, headless=True)
|
||
|
||
if cookie and len(cookie) > 100:
|
||
self.cookie = cookie
|
||
self.root.after(0, lambda: self.update_cookie_status(True))
|
||
self.log("✓ Cookie自动获取成功", "SUCCESS")
|
||
return True
|
||
else:
|
||
self.log("✗ 自动获取Cookie失败", "ERROR")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.log(f"✗ 自动获取Cookie失败: {str(e)}", "ERROR")
|
||
return False
|
||
|
||
def start_download(self):
|
||
url = self.url_entry.get().strip()
|
||
if not url:
|
||
messagebox.showwarning("提示", "请输入分享链接!")
|
||
return
|
||
|
||
# 确保有Cookie
|
||
if not self.ensure_cookie():
|
||
messagebox.showerror("错误", "无法获取Cookie,请检查网络或浏览器是否正常")
|
||
return
|
||
|
||
url = self.extract_url(url)
|
||
self.log(f"解析链接: {url}", "INFO")
|
||
|
||
self.is_downloading = True
|
||
self.download_btn.config(state=tk.DISABLED)
|
||
self.stop_btn.config(state=tk.NORMAL)
|
||
self.progress_var.set(0)
|
||
|
||
thread = threading.Thread(target=self.download_thread, args=(url,))
|
||
thread.daemon = True
|
||
thread.start()
|
||
|
||
def download_thread(self, url):
|
||
try:
|
||
self.log("开始下载任务...", "INFO")
|
||
self.status_label.config(text="正在下载...")
|
||
|
||
cmd = [PYTHON_PATH, "-m", "f2", "dy"]
|
||
|
||
mode = self.mode_var.get()
|
||
cmd.extend(["-M", mode])
|
||
cmd.extend(["-u", url])
|
||
|
||
download_dir = self.dir_entry.get().strip()
|
||
cmd.extend(["-p", download_dir])
|
||
|
||
# 使用自动获取的Cookie
|
||
if self.cookie:
|
||
cmd.extend(["-k", self.cookie])
|
||
|
||
if self.download_music.get():
|
||
cmd.extend(["-m", "true"])
|
||
if self.download_cover.get():
|
||
cmd.extend(["-v", "true"])
|
||
|
||
limit = self.limit_entry.get().strip()
|
||
if limit and limit != "0":
|
||
cmd.extend(["-o", limit])
|
||
|
||
self.log("执行下载命令...", "INFO")
|
||
|
||
env = os.environ.copy()
|
||
env["PYTHONIOENCODING"] = "utf-8"
|
||
|
||
process = subprocess.Popen(
|
||
cmd,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
encoding='utf-8',
|
||
errors='replace',
|
||
env=env,
|
||
bufsize=1
|
||
)
|
||
|
||
download_failed = False
|
||
|
||
while self.is_downloading:
|
||
if process.stdout is None:
|
||
break
|
||
|
||
line = process.stdout.readline()
|
||
if not line and process.poll() is not None:
|
||
break
|
||
|
||
if line:
|
||
line = line.strip()
|
||
if line:
|
||
self.log(line, "INFO")
|
||
|
||
if "下载" in line or "成功" in line or "保存" in line:
|
||
self.progress_var.set(min(self.progress_var.get() + 10, 90))
|
||
|
||
# 检测Cookie相关的错误
|
||
if "cookie" in line.lower() or "登录" in line or "失败" in line:
|
||
download_failed = True
|
||
|
||
if not self.is_downloading:
|
||
process.terminate()
|
||
self.log("下载已停止", "WARNING")
|
||
elif process.returncode == 0:
|
||
self.progress_var.set(100)
|
||
self.log("✓ 下载完成!", "SUCCESS")
|
||
self.status_label.config(text="下载完成!")
|
||
self.root.after(0, lambda: messagebox.showinfo("完成", f"下载完成!\n\n保存位置: {download_dir}"))
|
||
elif download_failed:
|
||
self.log("检测到Cookie可能过期,尝试刷新...", "WARNING")
|
||
# 重新获取Cookie
|
||
from auto_cookie import get_douyin_cookie_auto
|
||
new_cookie = get_douyin_cookie_auto(force_refresh=True, headless=True)
|
||
if new_cookie:
|
||
self.cookie = new_cookie
|
||
self.log("Cookie已刷新,请重试下载", "INFO")
|
||
self.status_label.config(text="Cookie已刷新,请重试")
|
||
else:
|
||
self.log("下载过程中出现错误", "WARNING")
|
||
self.status_label.config(text="下载失败")
|
||
|
||
except Exception as e:
|
||
self.log(f"错误: {str(e)}", "ERROR")
|
||
self.status_label.config(text="下载失败")
|
||
self.root.after(0, lambda: messagebox.showerror("错误", f"下载失败:\n{str(e)}"))
|
||
finally:
|
||
self.is_downloading = False
|
||
self.download_btn.config(state=tk.NORMAL)
|
||
self.stop_btn.config(state=tk.DISABLED)
|
||
|
||
def stop_download(self):
|
||
self.is_downloading = False
|
||
|
||
def main():
|
||
root = tk.Tk()
|
||
app = DouyinDownloaderUI(root)
|
||
root.mainloop()
|
||
|
||
if __name__ == "__main__":
|
||
main()
|