Xử lý Dữ liệu Phụ đề ASS Song ngữ với Python

Tệp phụ đề ASS (Advanced SubStation Alpha) thường được sử dụng rộng rãi, đặc biệt là trong các bản dịch anime và video. Chúng có cấu trúc đặc biệt để hỗ trợ định dạng văn bản nâng cao, vị trí và hiệu ứng động. Hiểu cấu trúc của chúng là bước đầu tiên để xử lý dữ liệu.

Một tệp ASS điển hình bao gồm các phần chính sau:

  • [Script Info]: Chứa thông tin chung về tệp phụ đề như tiêu đề, tác giả, mô tả và cài đặt định dạng.
  • [V4+ Styles]: Định nghĩa các kiểu dáng cho phụ đề, bao gồm phông chữ, kích thước, màu sắc, căn chỉnh và các hiệu ứng.
  • [Events]: Đây là phần cốt lõi chứa nội dung phụ đề thực tế (các đoạn hội thoại), thời gian hiển thị và ẩn, cùng với các hiệu ứng đặc biệt áp dụng cho từng dòng.

Trong bối cảnh bài viết này, chúng ta sẽ tập trung vào phần [Events]. Giả sử chúng ta có một tệp phụ đề song ngữ Nhật-Trung, nơi các dòng phụ đề của mỗi ngôn ngữ được hiển thị độc lập. Mục tiêu là sử dụng Python để trích xuất thông tin từ phần [Events] và đối chiếu các cặp phụ đề song ngữ này để dễ đọc và phân tích hơn.

1. Đọc Tệp Phụ đề ASS

Mặc dù tệp ASS có thể mở bằng trình soạn thảo văn bản hoặc thậm chí Excel, việc phân tích cú pháp bằng các thư viện như pandas.read_csv thường không hiệu quả do cấu trúc phi tiêu chuẩn của chúng. Phương pháp hiệu quả nhất là đọc tệp từng dòng bằng hàm open() của Python.

import pandas as pd
import re

def doc_va_phan_tich_ass(duong_dan_tep):
    """
    Đọc và phân tích cú pháp tệp phụ đề ASS để trích xuất tiêu đề cột 
    và các dòng thoại, sau đó đối chiếu phụ đề song ngữ (Nhật-Trung).
    """
    cot_phu_de = []
    thong_tin_thoai_tho = [] # Lưu trữ tuple (so_dong, cac_phan_thoai)
    
    trong_phan_events = False

    try:
        # Sử dụng 'utf-8-sig' để xử lý Byte Order Mark (BOM) nếu có
        with open(duong_dan_tep, "r", encoding="utf-8-sig") as f:
            for chi_muc_dong, dong_goc in enumerate(f):
                dong_sach = dong_goc.strip()
                if not dong_sach: # Bỏ qua các dòng trống
                    continue

                if dong_sach == "[Events]":
                    trong_phan_events = True
                    continue
                elif dong_sach.startswith("[") and dong_sach.endswith("]"): # Phát hiện phần mới
                    trong_phan_events = False
                    continue

                if trong_phan_events:
                    if dong_sach.startswith("Format:"):
                        cot_phu_de = [header.strip() for header in dong_sach.split(":", 1)[1].split(",")]
                    elif dong_sach.startswith("Dialogue:"):
                        if not cot_phu_de:
                            print(f"Cảnh báo: Dòng thoại tìm thấy trước khi Format: được định nghĩa tại dòng {chi_muc_dong}. Bỏ qua: {dong_sach}")
                            continue
                        
                        noi_dung_thoai = dong_sach.split(":", 1)[1]
                        # Chia theo dấu phẩy cho tất cả các trường trừ trường cuối cùng (Text)
                        # Số lần chia là len(cot_phu_de) - 1
                        cac_phan_thoai = noi_dung_thoai.split(",", len(cot_phu_de) - 1)
                        
                        if len(cac_phan_thoai) == len(cot_phu_de):
                            thong_tin_thoai_tho.append((chi_muc_dong, cac_phan_thoai))
                        else:
                            print(f"Cảnh báo: Không thể phân tích dòng thoại đầy đủ tại dòng {chi_muc_dong}. Số cột không khớp. Bỏ qua: {dong_sach}")
                            
    except FileNotFoundError:
        print(f"Lỗi: Không tìm thấy tệp tại đường dẫn: {duong_dan_tep}")
        return pd.DataFrame()
    except Exception as e:
        print(f"Đã xảy ra lỗi khi đọc tệp: {e}")
        return pd.DataFrame()

    if not cot_phu_de:
        print("Lỗi: Không tìm thấy dòng Format trong phần [Events]. Không thể xử lý.")
        return pd.DataFrame()

    if not thong_tin_thoai_tho:
        print("Không tìm thấy bất kỳ dòng thoại nào trong phần [Events].")
        return pd.DataFrame()

    # Tách phụ đề Nhật và Trung dựa trên chỉ mục dòng, mô phỏng cách tiếp cận của bài toán gốc.
    # Đây là một giả định dựa trên cấu trúc tệp cụ thể của ví dụ.
    phu_de_tieng_nhat = [data for chi_muc_dong, data in thong_tin_thoai_tho if 80 < chi_muc_dong < 185]
    phu_de_tieng_trung = [data for chi_muc_dong, data in thong_tin_thoai_tho if 489 <= chi_muc_dong <= 592]

    # Xen kẽ các mục phụ đề tiếng Nhật và tiếng Trung đã trích xuất
    du_lieu_phu_de_xen_ke = []
    so_luong_phu_de_lon_nhat = max(len(phu_de_tieng_nhat), len(phu_de_tieng_trung))

    for i in range(so_luong_phu_de_lon_nhat):
        if i < len(phu_de_tieng_nhat):
            du_lieu_phu_de_xen_ke.append(phu_de_tieng_nhat[i])
        if i < len(phu_de_tieng_trung):
            du_lieu_phu_de_xen_ke.append(phu_de_tieng_trung[i])
            
    if not du_lieu_phu_de_xen_ke:
        print("Không có phụ đề nào được đối chiếu sau khi lọc theo phạm vi dòng.")
        return pd.DataFrame()

    # Tạo DataFrame từ dữ liệu đã đối chiếu
    bang_du_lieu_phu_de = pd.DataFrame(du_lieu_phu_de_xen_ke, columns=cot_phu_de)
    
    return bang_du_lieu_phu_de

2. Xử lý và Tinh chỉnh DataFrame

Sau khi tạo DataFrame, chúng ta có thể cần thực hiện một số bước để làm sạch và tinh chỉnh dữ liệu. Điều này bao gồm việc loại bỏ các cột không cần thiết để chỉ giữ lại thông tin quan trọng như thời gian, kiểu và nội dung văn bản.

# Đường dẫn đến tệp phụ đề ASS của bạn
duong_dan_tep_ass = "D://pycharm//practice//Shirokuma Cafe.ass" 

# Gọi hàm để xử lý tệp
df_phu_de = doc_va_phan_tich_ass(duong_dan_tep_ass)

if not df_phu_de.empty:
    # Hiển thị tất cả các cột của DataFrame để kiểm tra
    pd.set_option("display.max_columns", None)
    print("DataFrame gốc:")
    print(df_phu_de.head())
    print("\nThông tin DataFrame gốc:")
    df_phu_de.info()

    # Loại bỏ các cột không cần thiết
    cot_can_bo = [" Layer", " Name", " MarginL", " MarginR", " MarginV", " Effect"]
    df_da_loc = df_phu_de.drop(columns=[col for col in cot_can_bo if col in df_phu_de.columns])
    
    print("\nDataFrame sau khi loại bỏ cột:")
    print(df_da_loc.head())
    print("\nThông tin DataFrame sau khi loại bỏ cột:")
    df_da_loc.info()

    # Xuất DataFrame ra tệp văn bản và Excel
    # Sử dụng ký tự tab làm dấu phân cách cho tệp TXT
    df_da_loc.to_csv("phu_de_output.txt", sep="\t", index=False, encoding="utf-8") 
    df_da_loc.to_excel("phu_de_output.xlsx", index=False, engine='xlsxwriter') # engine='xlsxwriter' cho hiệu suất tốt hơn
    
    print("\nDữ liệu phụ đề đã được xuất ra 'phu_de_output.txt' và 'phu_de_output.xlsx'.")
else:
    print("Không thể tạo DataFrame phụ đề. Vui lòng kiểm tra các cảnh báo/lỗi trên.")

Thẻ: python ASS Subtitles Data Processing Pandas Subtitle Parsing

Đăng vào ngày 17 tháng 05 lúc 21:19