OmniSub / PLAN.md
STBack23's picture
Upload OmniSub source
b5c8312 verified
|
Raw
History Blame Contribute Delete
13 kB

PLAN — OmniSub (dịch SRT ZH→VI bằng Qwen3-Omni)

Tài liệu thiết kế chi tiết. Quyết định đã chốt: cloud-first (Colab L4 24GB), Qwen3-Omni 4-bit làm tất cả (nghe giọng + nhìn cảnh + dịch), bỏ regex xưng hô của bản cũ.


0. Quyết định đã chốt

  • Model: Qwen/Qwen3-Omni-30B-A3B-Instruct (bản Instruct, 4-bit).
  • OCR sửa phụ đề cháy hình (Bước 1, tùy chọn): giữ lại nhưng MẶC ĐỊNH TẮT (correct_ocr: false), bật khi cần qua config/CLI. Nguồn SRT hiện đã sạch.
  • Tên file ra: giữ nếp cũ *.vi.srt (+ *.vi.srt.report.json).

1. Mục tiêu

  1. Dịch SRT Trung → Việt tự nhiên, đúng văn nói, súc tích.
  2. Xưng hô đúng vai vế nhờ:
    • Nghe giọng: ai đang nói (diarization) + giới/tuổi/cảm xúc/vai vế (Omni nghe trực tiếp).
    • Nhìn ngữ cảnh: frame video của cảnh đang dịch.
  3. Nhất quán tên riêngcách xưng hô xuyên suốt phim.
  4. Phụ đề vừa thời lượng hiển thị (chars/giây, số dòng).

2. Bài học từ Gemma-4 (giữ / bỏ)

Giữ (port sạch):

  • parse_srt, write_srt, subtitle_char_budget, _seconds_to_srt_time — I/O SRT vững.
  • group_scenes, merge_adjacent_pairs — gom cảnh, ghép cue vụn (sentence-aware, same-speaker).
  • Pattern chat_json + _extract_json — parse JSON từ output model bền bỉ.
  • Cấu trúc Colab + Google Drive (Phim/, Cache/).

Bỏ / thay thế:

  • RelationshipRegistry, _CJK_ROMANCE_RE, _CJK_FAMILY_RE, _VN_ANH_EM_RE, enforce_cue (hàng trăm dòng regex khóa anh/em vs mẹ/con) → thay bằng hồ sơ giọng + ngữ cảnh đa phương thức.
  • estimate_speaker_demographics (wav2vec2 age/gender) → Omni nghe hiểu trực tiếp.
  • _patch_speechbrain_lazy (vá Windows) → Colab Linux không cần.
  • 🔄 Gemma 4 12B (llama.cpp) → Qwen3-Omni-30B-A3B (transformers).

3. Kiến trúc & luồng 5 bước

INPUT: video.mp4 + video.srt (ZH)
                │
┌───────────────────────────────────────────────────────────────────────────────┐
│ Bước 1  Chuẩn bị SRT          srt.py + scenes.py                              │
│         parse → gom cảnh → ghép cue vụn → char budget                         │
│         [tùy chọn] OCR sửa phụ đề cháy hình (correct.py) — MẶC ĐỊNH TẮT     │
└───────────────────────────────────────────────────────────────────────────────┘
                │
┌───────────────────────────────────────────────────────────────────────────────┐
│ Bước 2  Phân tích người nói   diarize.py                                      │
│         pyannote community-1 → speaker turns → gán cue.speaker              │
└───────────────────────────────────────────────────────────────────────────────┘
                │
┌───────────────────────────────────────────────────────────────────────────────┐
│ Bước 3  Hồ sơ giọng           profiles.py                                     │
│         Qwen3-Omni nghe 3–5 clip/speaker → VoiceProfile                       │
│         {gender, age_range, emotion, role_guess, register_hint}               │
└───────────────────────────────────────────────────────────────────────────────┘
                │
┌───────────────────────────────────────────────────────────────────────────────┐
│ Bước 4  Dịch đa phương thức   translate.py + scene_context.py                 │
│         Qwen3-Omni: audio cảnh + frames + cues + profiles + glossary (partial)│
│         → bản dịch VN + ghi chú xưng hô                                       │
└───────────────────────────────────────────────────────────────────────────────┘
                │
┌───────────────────────────────────────────────────────────────────────────────┐
│ Bước 5  Hậu kiểm & xuất       glossary.py + srt.py                            │
│         nhất quán tên/xưng hô → fit thời lượng → ghi file                     │
└───────────────────────────────────────────────────────────────────────────────┘
                │
OUTPUT: video.vi.srt + video.vi.srt.report.json + glossary.json
Bước Module Model Mặc định
1 — Chuẩn bị SRT srt.py, scenes.py, correct.py — / Omni (nếu bật OCR) OCR tắt
2 — Phân tích người nói diarize.py pyannote bật
3 — Hồ sơ giọng profiles.py Qwen3-Omni bật
4 — Dịch translate.py, scene_context.py Qwen3-Omni bật
5 — Hậu kiểm glossary.py — / Omni (rút gọn cue) bật

Vì sao vẫn cần pyannote (Bước 2)

Qwen3-Omni hiện chưa gán nhãn speaker ổn định trên cả phim (xác nhận qua tài liệu kỹ thuật & nghiên cứu HumanOmni-Speaker 2026 — cần adapter riêng). pyannote cho nhãn nhất quán để gắn hồ sơ giọng (Bước 3) cho đúng người ở mọi cảnh.

Vì sao một mô hình Omni cho Bước 3 & 4

Đưa audio đoạn cảnh + frame + cue cùng lúc → model "nghe" được tông giọng (ai đang nói với ai, tình cảm hay gắt gỏng) và "thấy" bối cảnh → tự chọn xưng hô. Không còn luật cứng.

4. Module chi tiết (src/omnisub/)

File Trách nhiệm Nguồn
srt.py Dataclass Cue, parse/write SRT, char budget Port từ bản cũ
scenes.py Scene, group_scenes, merge_adjacent_pairs Port, dọn lại
diarize.py Wrapper pyannote → SpeakerTurn, gán cue.speaker Port rút gọn
correct.py Bước 1 — OCR sửa phụ đề cháy hình (mặc định tắt) Port từ correct_scene
profiles.py VoiceProfile; trích clip/speaker; gọi Omni nghe → hồ sơ Mới
scene_context.py Trích frame (ffmpeg), chọn audio đoạn cảnh Port extract_frame + mới
translate.py Dựng prompt đa phương thức/cảnh, gọi Omni, parse kết quả Viết lại (bỏ regex)
glossary.py ★ Nhất quán tên riêng + xưng hô theo dữ liệu (không regex) Mới (thay NameRegistry)
backends/base.py Interface LLMBackend.chat_json(text, images, audio, …) Mới
backends/transformers_qwen.py Qwen3-Omni qua HF transformers (Colab) Mới
pipeline.py Điều phối Bước 1→5, logging, progress, stop Viết lại gọn
cli.py Argparse CLI Phỏng theo bản cũ

VoiceProfile (Bước 3) — cấu trúc đề xuất

@dataclass
class VoiceProfile:
    speaker: str            # "SPEAKER_01" (khớp pyannote)
    gender: str             # nam | nữ | trẻ em | chưa rõ
    age_range: str          # trẻ em | thiếu niên | thanh niên | trung niên | lớn tuổi
    emotion_baseline: str   # vd: điềm đạm / nóng nảy / dịu dàng
    role_guess: str         # vd: "phụ nữ trẻ, có vẻ là người yêu của SPEAKER_02"
    register_hint: str      # gợi ý xưng hô VN: "nên dùng em với SPEAKER_02"
    evidence: str           # vì sao (model tự giải thích ngắn)

Hồ sơ này được nhét vào prompt dịch để Omni giữ xưng hô nhất quán theo từng nhân vật.

Glossary (Bước 5) — thay regex bằng dữ liệu

  • Thu thập tên riêng (token CJK lặp lại) → khóa một cách phiên âm VN, tái dùng (giữ ý tưởng NameRegistry cũ nhưng tách bạch, có thể chỉnh tay qua glossary.json).
  • Cặp xưng hô giữa các speaker do model đề xuất (không hard-code), lưu vào glossary và đưa lại vào prompt các cảnh sau để khóa nhất quán — thay cho RelationshipRegistry.

5. Backend & mô hình (Colab L4 24GB)

  • Model: Qwen/Qwen3-Omni-30B-A3B-Instruct (Bước 3 & 4).
  • Lượng hóa: 4-bit (bitsandbytes hoặc checkpoint AWQ/GPTQ Int4) → ~17–18GB, vừa L4 24GB (chừa chỗ cho KV cache + encoder audio/vision). Cần kiểm tra checkpoint 4-bit sẵn có trên HF.
  • Audio: resample 16 kHz mono (đã có sẵn extract_audio bản cũ). Đưa audio sau text trong prompt (theo best practice Gemma/Qwen multimodal: ảnh trước text, audio sau text).
  • Vision: frame trích bằng ffmpeg, đưa trước text.
  • Tham số sampling: theo khuyến nghị Qwen (temperature ~0.7–1.0, top_p 0.95, top_k 64) — chốt khi test.
  • Drive: cache model ở Gemma/Cache/, phim ở Gemma/Phim/ (giữ nếp cũ).

6. Cấu hình (config.yaml)

models:
  omni: "Qwen/Qwen3-Omni-30B-A3B-Instruct"
  quant: "4bit"
  diarize: "pyannote/speaker-diarization-community-1"
translate:
  source_lang: "Chinese"
  target_lang: "Vietnamese"
  chars_per_sec: 22
  max_line_chars: 52
  max_lines: 2
  correct_ocr: false        # Bước 1 — OCR sửa phụ đề cháy hình (mặc định tắt)
  output_suffix: ".vi.srt"  # giữ nếp cũ
scene:
  max_gap: 1.5
  max_cues: 4
  max_dur: 20.0
profiling:
  clips_per_speaker: 5
  max_seconds_per_clip: 8
paths:
  drive_root: "/content/drive/MyDrive/Gemma"

7. Lộ trình (milestones)

  • M1 — Bước 1 Chuẩn bị SRT: scaffold thư mục, srt.py, scenes.py, config.yaml, backends/base.py. Done khi: parse SRT → gom cảnh → write SRT chạy được; python -m omnisub.cli --help.
  • M2 — Backend Omni (Colab): notebook tải Qwen3-Omni 4-bit; chat_json text-only; dịch thử 1 cảnh.- M3 — Bước 2 Diarization: port pyannote gọn; gán cue.speaker; xuất nhãn.
  • M4 — Bước 3 Voice Profiling ★: profiles.py — Omni nghe clip → VoiceProfile.
  • M5 — Bước 4 Dịch đa phương thức: translate.py ghép audio+frame+cue+profile; không regex.
  • M6 — Bước 5 Hậu kiểm: nhất quán tên/xưng hô; fit timing; xuất .vi.srt + report JSON.
  • M7 — Tài liệu & hoàn thiện: tinh chỉnh prompt, hoàn thiện README + notebook Colab.

8. Rủi ro & phương án

Rủi ro Phương án
Omni 4-bit không có checkpoint sẵn / chất lượng tụt Thử AWQ Int4; nếu kẹt → tách: Qwen3-Omni nghe giọng + Qwen3-VL dịch
L4 24GB tràn VRAM khi kèm audio+vision dài Giảm số frame/cảnh, cắt audio ≤ 8s/clip, giảm visual token budget
pyannote chậm với phim dài Chỉ diarize tới max(cue.end); cân nhắc NeMo Sortformer
Diarization gán sai người nói Cho phép sửa tay nhãn speaker trước Bước 3
Mất kết nối/ngắt phiên Colab giữa chừng Cache theo cảnh, cho chạy lại từ cảnh dở; lưu kết quả ra Drive

9. Quyết định (đã chốt — xem mục 0)

  • ✅ Qwen3-Omni -Instruct 4-bit.
  • ✅ OCR (Bước 1) mặc định tắt (correct_ocr: false), bật khi cần.
  • ✅ Tên file ra .vi.srt.

Việc cần kiểm tra trước M2

  • Xác nhận có checkpoint Qwen3-Omni-30B-A3B-Instruct 4-bit / AWQ Int4 trên Hugging Face. Nếu chưa có → phương án dự phòng: Qwen3-Omni (nghe giọng) + Qwen3-VL (dịch). (Xem mục 8.)