import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk
import os
import subprocess
import datetime
import srt
def log(text):
log_box.insert(tk.END, text + '\n')
log_box.see(tk.END)
root.update_idletasks()
def extract_audio_ffmpeg(video_path, audio_path="temp_audio.mp3"):
try:
cmd = ["ffmpeg", "-y", "-i", video_path, "-vn", "-acodec", "libmp3lame", audio_path]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except Exception as e:
log(f"FFmpeg error: {e}")
return False
def transcribe_audio(audio_path, use_gpu=False):
import whisper # Import bên trong để tránh lỗi khi đóng gói
model = whisper.load_model("base", device="cuda" if use_gpu else "cpu")
result = model.transcribe(audio_path, language="zh")
return result["segments"]
def segments_to_text(segments, translate=False, translator=None):
lines = []
for seg in segments:
content = seg['text'].strip()
if translate and translator:
try:
content = translator.translate(content, src='zh-cn', dest='vi').text
except Exception:
content = "[Lỗi dịch]"
lines.append(content)
return '\n'.join(lines)
def segments_to_srt(segments, translate=False, translator=None):
subtitles = []
for i, seg in enumerate(segments):
start = datetime.timedelta(seconds=int(seg['start']))
end = datetime.timedelta(seconds=int(seg['end']))
content = seg['text'].strip()
if translate and translator:
try:
content = translator.translate(content, src='zh-cn', dest='vi').text
except Exception:
content = "[Lỗi dịch]"
sub = srt.Subtitle(index=i+1, start=start, end=end, content=content)
subtitles.append(sub)
return srt.compose(subtitles)
def save_file(content, path):
with open(path, "w", encoding="utf-8") as f:
f.write(content)
def run_batch_process():
from googletrans import Translator # Import bên trong để tránh lỗi khi đóng gói
folder_path = video_folder_var.get()
output_dir = output_dir_var.get()
use_gpu = use_gpu_var.get() == "gpu"
export_cn = export_cn_var.get()
export_vi = export_vi_var.get()
file_format = format_var.get()
if not export_cn and not export_vi:
messagebox.showerror("Lỗi", "Bạn cần chọn ít nhất một định dạng phụ đề để xuất.")
return
if not os.path.isdir(folder_path):
messagebox.showerror("Lỗi", "Vui lòng chọn thư mục chứa video hợp lệ.")
return
if not os.path.exists(output_dir):
messagebox.showerror("Lỗi", "Vui lòng chọn thư mục xuất file.")
return
video_files = [f for f in os.listdir(folder_path) if f.lower().endswith(('.mp4', '.mkv', '.mov'))]
total = len(video_files)
if total == 0:
messagebox.showerror("Lỗi", "Không tìm thấy video hợp lệ trong thư mục.")
return
translator = Translator() if export_vi else None
progress_bar['value'] = 0
root.update_idletasks()
for idx, video_name in enumerate(video_files, 1):
log(f"▶ Video {idx}/{total}: {video_name}")
video_path = os.path.join(folder_path, video_name)
filename = os.path.splitext(video_name)[0]
audio_path = "temp_audio.mp3"
log("📤 Đang tách âm thanh bằng FFmpeg...")
if not extract_audio_ffmpeg(video_path, audio_path):
log(f"❌ Không thể tách âm thanh từ {video_name}")
continue
log(f"🧠 Đang nhận diện bằng Whisper ({'GPU' if use_gpu else 'CPU'})...")
try:
segments = transcribe_audio(audio_path, use_gpu=use_gpu)
except Exception as e:
log(f"❌ Lỗi nhận diện {video_name}: {e}")
continue
if export_cn:
log(f"✍️ Xuất phụ đề tiếng Trung ({file_format})...")
content = segments_to_srt(segments, False) if file_format == "srt" else segments_to_text(segments, False)
path = os.path.join(output_dir, f"{filename}-china.{file_format}")
save_file(content, path)
log(f"✅ Đã lưu: {path}")
if export_vi:
log(f"🌐 Xuất phụ đề tiếng Việt ({file_format})...")
content = segments_to_srt(segments, True, translator) if file_format == "srt" else segments_to_text(segments, True, translator)
path = os.path.join(output_dir, f"{filename}-vietnam.{file_format}")
save_file(content, path)
log(f"✅ Đã lưu: {path}")
if os.path.exists(audio_path):
os.remove(audio_path)
percent = int((idx / total) * 100)
progress_bar['value'] = percent
root.update_idletasks()
log("🎉 Hoàn tất xử lý tất cả video!")
messagebox.showinfo("Thành công", "Đã tạo phụ đề cho tất cả video trong thư mục!")
# ============ GUI ============
def center_window(root, width=800, height=620):
# 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}")
def start_gui():
global root, video_folder_var, output_dir_var, use_gpu_var
global export_cn_var, export_vi_var, format_var, progress_bar, log_box
root = tk.Tk()
root.title("Chinese Video Subtitle Maker - DarkCorners")
root.resizable(False, False)
center_window(root, 800, 620)
frame_top = tk.Frame(root)
frame_top.pack(pady=10)
tk.Label(frame_top, text="Thư mục chứa video:", font=("Arial", 10)).grid(row=0, column=0, sticky="w")
video_folder_var = tk.StringVar()
tk.Entry(frame_top, textvariable=video_folder_var, width=60).grid(row=0, column=1, padx=5)
tk.Button(frame_top, text="Chọn Thư Mục", command=lambda: video_folder_var.set(filedialog.askdirectory())).grid(row=0, column=2)
tk.Label(frame_top, text="Thư mục xuất file:", font=("Arial", 10)).grid(row=1, column=0, sticky="w", pady=10)
output_dir_var = tk.StringVar()
tk.Entry(frame_top, textvariable=output_dir_var, width=60).grid(row=1, column=1, padx=5)
tk.Button(frame_top, text="Chọn Thư Mục", command=lambda: output_dir_var.set(filedialog.askdirectory())).grid(row=1, column=2)
row_opts = tk.Frame(frame_top)
row_opts.grid(row=2, column=0, columnspan=3, pady=10, sticky="w")
tk.Label(row_opts, text="Chế độ xử lý:", font=("Arial", 10)).pack(side="left", padx=(0, 10))
use_gpu_var = tk.StringVar(value="cpu")
tk.Radiobutton(row_opts, text="CPU", variable=use_gpu_var, value="cpu").pack(side="left")
tk.Radiobutton(row_opts, text="GPU (nếu có)", variable=use_gpu_var, value="gpu").pack(side="left", padx=(5, 20))
tk.Label(row_opts, text="Xuất phụ đề:", font=("Arial", 10)).pack(side="left", padx=(20, 10))
export_cn_var = tk.BooleanVar(value=True)
export_vi_var = tk.BooleanVar(value=True)
tk.Checkbutton(row_opts, text="Tiếng Trung", variable=export_cn_var).pack(side="left")
tk.Checkbutton(row_opts, text="Tiếng Việt", variable=export_vi_var).pack(side="left", padx=(10, 0))
row_format = tk.Frame(root)
row_format.pack(pady=(5, 5))
tk.Label(row_format, text="Định dạng xuất:", font=("Arial", 10)).pack(side="left")
format_var = tk.StringVar(value="srt")
tk.Radiobutton(row_format, text=".srt", variable=format_var, value="srt").pack(side="left", padx=10)
tk.Radiobutton(row_format, text=".txt", variable=format_var, value="txt").pack(side="left", padx=10)
tk.Button(root, text="Tạo Phụ Đề Cho Tất Cả Video", command=run_batch_process,
bg="#4CAF50", fg="white", font=("Arial", 12, "bold"), width=40).pack(pady=10)
progress_bar = ttk.Progressbar(root, orient="horizontal", length=600, mode="determinate")
progress_bar.pack(pady=5)
tk.Label(root, text="Logs:", font=("Arial", 10)).pack(anchor="w", padx=15)
log_box = scrolledtext.ScrolledText(root, width=95, height=14, font=("Consolas", 9))
log_box.pack(padx=15, pady=5)
root.mainloop()
if __name__ == "__main__":
start_gui()