Chinese Video Subtitle Maker – DarkCorners

Date: 17/12/2025

Category: Python

  • Phần mềm hỗ trợ tạo sub cho video.
  • Hỗ trợ sử dụng CPU hoặc CPU & GPU (Nếu có).
  • Hỗ trợ xuất ra phụ đề 2 ngôn ngữ TRUNG – VIỆT.
  1. Khởi động phần mềm.
  2. Chọn thư mục chứa toàn bộ video cần tạo sub.
  3. Chọn thư mục xuất file sub.
  4. Chọn chế độ xử lý là CPU hay CPU & GPU.
  5. Chọn các loại phụ đề sẽ xuất ra TRUNG – VIỆT.
  6. Chọn định dạng file sub xuất ra SRT hay TXT.
  7. Nhấn nút [Tạo Phụ Đề Cho Tất Cả Video].
  8. Theo dõi lịch trình xử lý.
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()
pyinstaller --noconsole --onefile --windowed --add-data "4-ChineseVideoSubtitleMaker-icon.ico;." --icon=4-ChineseVideoSubtitleMaker-icon.ico 4-ChineseVideoSubtitleMaker-Source.py

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