版本: 1.0.0
最後更新: 2026-01-30
狀態: 規劃階段
Subtitle Creator 是一個全自動化的字幕生成工具,旨在解決影片內容創作者、翻譯工作者在處理多語言字幕時的痛點。透過最新的 AI 語音辨識與翻譯技術,實現從影片到多語言字幕的一站式處理。
| 目標 | 說明 |
|---|---|
| 自動化 | 最小化人工介入,實現批次處理 |
| 精準度 | 利用 WhisperX 達成 word-level 時間軸對齊 |
| 多語言 | 支援英文/日文來源,輸出繁體中文字幕 |
| 本地運行 | 完全離線運作,保護隱私與資料安全 |
使用者情境 1: 動畫字幕組 ───────────────────────── 輸入: 一整季 12 集日文動畫 (.mkv) 輸出: 對應時間軸的繁體中文 .srt 字幕檔
使用者情境 2: 教育內容創作者
─────────────────────────
輸入: 英文教學影片資料夾 (.mp4)
輸出: 英文原文 + 繁體中文雙語字幕
┌─────────────────────────────────────────────────────────────────┐ │ Subtitle Creator 功能架構 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ [1] 影片掃描 [2] 音軌擷取 [3] 語音辨識 │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │ │ │ Scanner │───▶│ FFmpeg │───▶│ WhisperX │ │ │ │ Module │ │ Extract │ │ (STT+VAD) │ │ │ └─────────┘ └─────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ [4] 翻譯處理 │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ TranslateGemma │ │ │ │ (12B Model) │ │ │ └─────────────────┘ │ │ │ │ │ ▼ │ │ [5] SRT 生成 │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ SRT Writer │ │ │ │ (時間軸對齊) │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
| 項目 | 規格 |
|---|---|
| 支援格式 | .mp4, .mkv, .ts, .avi, .mov, .webm, .flv |
| 掃描模式 | 遞迴掃描 / 單層掃描 (可配置) |
| 過濾規則 | 支援 glob pattern (如 *_raw.mp4) |
| 輸出 | 影片檔案清單含完整路徑與元資料 |
| 項目 | 規格 |
|---|---|
| 工具 | FFmpeg (透過 ffmpeg-python 封裝) |
| 輸出格式 | WAV (16kHz, mono) - WhisperX 最佳輸入格式 |
| 多音軌處理 | 自動選擇主音軌 / 可指定音軌索引 |
| 效能 | 支援硬體加速 (NVENC/QSV) |
| 項目 | 規格 |
|---|---|
| 模型 | WhisperX (基於 Whisper large-v3) |
| 支援語言 | 英文 (EN), 日文 (JA) |
| 時間軸精度 | Word-level timestamps (透過 WAV2VEC2 強制對齊) |
| VAD | Silero VAD 預處理,提升長音訊準確度 |
| 輸出 | 包含時間戳記的逐字稿 JSON |
| 項目 | 規格 |
|---|---|
| 模型 | TranslateGemma 12B (Google 開源) |
| 來源語言 | 英文 (EN), 日文 (JA) |
| 目標語言 | 繁體中文 (ZH-TW) |
| 處理方式 | 逐句翻譯,保留時間軸資訊 |
| 優化 | 支援 4-bit 量化,降低 VRAM 需求 |
| 項目 | 規格 |
|---|---|
| 格式 | SRT (SubRip Subtitle) |
| 編碼 | UTF-8 with BOM (相容性最佳) |
| 時間格式 | HH:MM:SS,mmm |
| 輸出選項 | 原文字幕 / 翻譯字幕 / 雙語字幕 |
⚠️ 關鍵技術決策: 時間軸在語音辨識階段即可獲得
WhisperX 處理流程: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[音訊輸入] │ ▼ [1. Whisper 轉錄] ─────▶ 獲得 segment-level 時間軸 (粗略) │ ▼ [2. VAD 切割] ─────────▶ 識別語音活動區間 │ ▼ [3. WAV2VEC2 對齊] ────▶ 強制對齊獲得 word-level 時間軸 (精準) │ ▼ [輸出: 帶時間戳的逐字稿]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
時間軸保留策略: ┌────────────────────────────────────────────────────────┐ │ 原文 Segment: │ │ "Hello world" @ [00:01.200 → 00:02.800] │ │ │ │ 翻譯後 Segment: │ │ "你好世界" @ [00:01.200 → 00:02.800] ← 時間軸繼承 │ └────────────────────────────────────────────────────────┘
| 模型 | 準確度 | 速度 | Word-level | 本地運行 | 推薦度 |
|---|---|---|---|---|---|
| OpenAI Whisper | ⭐⭐⭐⭐ | ⭐⭐ | ❌ 需額外處理 | ✅ | ★★★☆☆ |
| WhisperX | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ✅ 內建 | ✅ | ★★★★★ |
| faster-whisper | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ❌ 需額外處理 | ✅ | ★★★★☆ |
| GPT-4o-transcribe | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ✅ | ❌ API | ★★★☆☆ |
選擇 WhisperX 原因: 內建 word-level timestamps、VAD、Speaker Diarization,且基於 large-v3 模型,準確度最佳
| 模型 | 中文品質 | 日文支援 | VRAM 需求 | 本地運行 | 推薦度 |
|---|---|---|---|---|---|
| TranslateGemma 12B | ⭐⭐⭐⭐⭐ | ✅ | ~8GB (4-bit) | ✅ | ★★★★★ |
| TranslateGemma 4B | ⭐⭐⭐⭐ | ✅ | ~3GB (4-bit) | ✅ | ★★★★☆ |
| NLLB-200 | ⭐⭐⭐ | ✅ | ~4GB | ✅ | ★★★☆☆ |
| DeepL API | ⭐⭐⭐⭐⭐ | ✅ | N/A | ❌ API | ★★★☆☆ |
選擇 TranslateGemma 12B 原因: Google 於 2026/01 發布的最新開源翻譯模型,支援 55 種語言,中文翻譯品質優異
╔══════════════════════════════════════════════════════════════╗ ║ SUBTITLE CREATOR TECH STACK ║ ╠══════════════════════════════════════════════════════════════╣ ║ ║ ║ ┌─────────────────────────────────────────────────────────┐ ║ ║ │ 程式語言 │ ║ ║ │ ─────────────────────────────────────────────────────── │ ║ ║ │ Python 3.11+ (型別提示完整支援、效能優化) │ ║ ║ └─────────────────────────────────────────────────────────┘ ║ ║ ║ ║ ┌─────────────────────────────────────────────────────────┐ ║ ║ │ AI/ML 模型 │ ║ ║ │ ─────────────────────────────────────────────────────── │ ║ ║ │ • WhisperX (large-v3) - 語音辨識 + 時間軸對齊 │ ║ ║ │ • TranslateGemma (12B) - 多語言翻譯 │ ║ ║ │ • Silero VAD - 語音活動偵測 │ ║ ║ └─────────────────────────────────────────────────────────┘ ║ ║ ║ ║ ┌─────────────────────────────────────────────────────────┐ ║ ║ │ 核心工具 │ ║ ║ │ ─────────────────────────────────────────────────────── │ ║ ║ │ • FFmpeg - 音訊擷取與轉換 │ ║ ║ │ • ffmpeg-python - Python FFmpeg 封裝 │ ║ ║ │ • PyTorch 2.x - 深度學習框架 │ ║ ║ │ • Transformers (HF) - 模型載入與推論 │ ║ ║ └─────────────────────────────────────────────────────────┘ ║ ║ ║ ║ ┌─────────────────────────────────────────────────────────┐ ║ ║ │ 開發工具 │ ║ ║ │ ─────────────────────────────────────────────────────── │ ║ ║ │ • Typer - CLI 框架 │ ║ ║ │ • Rich - 終端機美化輸出 │ ║ ║ │ • Pydantic v2 - 資料驗證與設定管理 │ ║ ║ │ • pytest - 測試框架 │ ║ ║ │ • uv - 套件管理 (取代 pip) │ ║ ║ └─────────────────────────────────────────────────────────┘ ║ ║ ║ ╚══════════════════════════════════════════════════════════════╝
| 等級 | GPU | VRAM | RAM | 適用情境 |
|---|---|---|---|---|
| 最低需求 | GTX 1660 | 6GB | 16GB | 僅語音辨識,翻譯使用 4B 模型 |
| 建議配置 | RTX 3080 | 10GB | 32GB | 完整功能,12B 翻譯模型 (4-bit) |
| 最佳配置 | RTX 4090 | 24GB | 64GB | 批次處理,fp16 推論 |
| CPU 模式 | N/A | N/A | 32GB+ | 可運行但極慢,不建議 |
flowchart TB subgraph Input["📂 輸入層"] A[影片資料夾] --> B[檔案掃描器] B --> C{影片檔案佇列} end
subgraph Processing["⚙️ 處理層"]
C --> D[FFmpeg 音軌擷取]
D --> E[WAV 音訊檔]
E --> F[WhisperX 語音辨識]
F --> G[帶時間軸的原文逐字稿]
G --> H{需要翻譯?}
H -->|是| I[TranslateGemma 翻譯]
H -->|否| J[SRT 生成器]
I --> J
end
subgraph Output["📤 輸出層"]
J --> K[原文 SRT]
J --> L[翻譯 SRT]
J --> M[雙語 SRT]
end
subgraph Config["🔧 配置層"]
N[config.yaml] -.-> B
N -.-> D
N -.-> F
N -.-> I
end
style Input fill:#e1f5fe
style Processing fill:#fff3e0
style Output fill:#e8f5e9
style Config fill:#f3e5f5
sequenceDiagram participant User as 使用者 participant CLI as CLI 介面 participant Scanner as 檔案掃描器 participant FFmpeg as FFmpeg participant WhisperX as WhisperX participant Translator as TranslateGemma participant Writer as SRT Writer
User->>CLI: subtitle-creator run ./videos --lang ja --target zh-tw
CLI->>Scanner: 掃描資料夾
Scanner-->>CLI: 找到 5 個影片檔
loop 每個影片檔
CLI->>FFmpeg: 擷取音軌
FFmpeg-->>CLI: output.wav
CLI->>WhisperX: 辨識語音
WhisperX-->>CLI: 逐字稿 + 時間軸
CLI->>Translator: 翻譯字幕
Translator-->>CLI: 翻譯結果
CLI->>Writer: 生成 SRT
Writer-->>CLI: video.srt
end
CLI-->>User: ✅ 完成! 已生成 5 個字幕檔
classDiagram class SubtitleCreator { -config: Config -scanner: VideoScanner -extractor: AudioExtractor -transcriber: Transcriber -translator: Translator -writer: SRTWriter +run(input_path, options) +process_single(video_path) }
class VideoScanner {
-supported_formats: list
-recursive: bool
+scan(directory) list~VideoFile~
+filter(pattern) list~VideoFile~
}
class AudioExtractor {
-ffmpeg_path: str
-output_format: str
+extract(video_path) AudioFile
+get_audio_info(video_path) AudioMeta
}
class Transcriber {
-model: WhisperXModel
-language: str
-device: str
+transcribe(audio_path) Transcript
+align(transcript) AlignedTranscript
}
class Translator {
-model: TranslateGemmaModel
-source_lang: str
-target_lang: str
+translate(text) str
+translate_segments(segments) list~Segment~
}
class SRTWriter {
-encoding: str
-style: SRTStyle
+write(segments, output_path)
+format_timestamp(ms) str
}
class Segment {
+index: int
+start_time: float
+end_time: float
+text: str
+translation: str
}
SubtitleCreator --> VideoScanner
SubtitleCreator --> AudioExtractor
SubtitleCreator --> Transcriber
SubtitleCreator --> Translator
SubtitleCreator --> SRTWriter
SRTWriter --> Segment
Transcriber --> Segment
Translator --> Segment
from pathlib import Path from dataclasses import dataclass from typing import Iterator
@dataclass class VideoFile: """影片檔案資訊""" path: Path filename: str extension: str size_bytes: int duration_seconds: float | None = None
class VideoScanner: """影片檔案掃描器"""
SUPPORTED_FORMATS = {'.mp4', '.mkv', '.ts', '.avi', '.mov', '.webm', '.flv'}
def __init__(self, recursive: bool = True):
self.recursive = recursive
def scan(self, directory: Path) -> Iterator[VideoFile]:
"""
掃描目錄下的所有影片檔案
Args:
directory: 要掃描的目錄路徑
Yields:
VideoFile: 影片檔案資訊物件
"""
...
def filter_by_pattern(
self,
files: list[VideoFile],
pattern: str
) -> list[VideoFile]:
"""根據 glob pattern 過濾檔案"""
...
from pathlib import Path from dataclasses import dataclass
@dataclass class AudioConfig: """音訊擷取設定""" sample_rate: int = 16000 # WhisperX 最佳輸入 channels: int = 1 # 單聲道 format: str = 'wav' codec: str = 'pcm_s16le'
@dataclass class AudioFile: """音訊檔案資訊""" path: Path duration_seconds: float sample_rate: int channels: int
class AudioExtractor: """FFmpeg 音訊擷取器"""
def __init__(self, config: AudioConfig | None = None):
self.config = config or AudioConfig()
self._check_ffmpeg()
def extract(
self,
video_path: Path,
output_path: Path | None = None
) -> AudioFile:
"""
從影片擷取音軌
Args:
video_path: 影片檔案路徑
output_path: 輸出音訊路徑 (預設同目錄)
Returns:
AudioFile: 音訊檔案資訊
"""
...
def get_audio_streams(self, video_path: Path) -> list[dict]:
"""取得影片中的所有音軌資訊"""
...
from pathlib import Path from dataclasses import dataclass, field from enum import Enum
class Language(str, Enum): ENGLISH = "en" JAPANESE = "ja"
@dataclass class Word: """單詞層級資訊""" word: str start: float end: float confidence: float
@dataclass class Segment: """字幕段落""" index: int start: float # 秒 end: float # 秒 text: str words: list[Word] = field(default_factory=list) translation: str | None = None
@dataclass class Transcript: """轉錄結果""" language: Language segments: list[Segment] duration: float
class Transcriber: """WhisperX 語音辨識器"""
def __init__(
self,
model_size: str = "large-v3",
device: str = "cuda",
compute_type: str = "float16",
language: Language | None = None
):
self.model_size = model_size
self.device = device
self.compute_type = compute_type
self.language = language
self._model = None
self._align_model = None
def load_model(self) -> None:
"""載入 WhisperX 模型"""
...
def transcribe(self, audio_path: Path) -> Transcript:
"""
轉錄音訊檔案
Args:
audio_path: 音訊檔案路徑
Returns:
Transcript: 包含時間軸的轉錄結果
"""
...
def align(self, transcript: Transcript, audio_path: Path) -> Transcript:
"""對齊時間軸至 word-level"""
...
from dataclasses import dataclass from enum import Enum
class TargetLanguage(str, Enum): TRADITIONAL_CHINESE = "zh-tw" SIMPLIFIED_CHINESE = "zh-cn"
@dataclass class TranslatorConfig: """翻譯器設定""" model_name: str = "google/translategemma-12b" device: str = "cuda" load_in_4bit: bool = True # 啟用 4-bit 量化 max_length: int = 512 batch_size: int = 8 # 批次翻譯提升效率
class Translator: """TranslateGemma 翻譯器"""
def __init__(self, config: TranslatorConfig | None = None):
self.config = config or TranslatorConfig()
self._model = None
self._tokenizer = None
def load_model(self) -> None:
"""載入 TranslateGemma 模型"""
...
def translate(
self,
text: str,
source_lang: str,
target_lang: TargetLanguage = TargetLanguage.TRADITIONAL_CHINESE
) -> str:
"""
翻譯單一文字
Args:
text: 原文
source_lang: 來源語言代碼
target_lang: 目標語言
Returns:
翻譯後的文字
"""
...
def translate_segments(
self,
segments: list[Segment],
source_lang: str,
target_lang: TargetLanguage = TargetLanguage.TRADITIONAL_CHINESE
) -> list[Segment]:
"""批次翻譯字幕段落,保留時間軸"""
...
from pathlib import Path from dataclasses import dataclass from enum import Enum
class SRTStyle(str, Enum): SOURCE_ONLY = "source" # 僅原文 TRANSLATION_ONLY = "translation" # 僅翻譯 BILINGUAL = "bilingual" # 雙語 (原文在上) BILINGUAL_REVERSE = "bilingual_reverse" # 雙語 (翻譯在上)
@dataclass class SRTConfig: """SRT 輸出設定""" style: SRTStyle = SRTStyle.TRANSLATION_ONLY encoding: str = "utf-8-sig" # UTF-8 with BOM max_chars_per_line: int = 42 # 每行最大字元數 max_lines: int = 2 # 每段最多行數
class SRTWriter: """SRT 字幕生成器"""
def __init__(self, config: SRTConfig | None = None):
self.config = config or SRTConfig()
def write(
self,
segments: list[Segment],
output_path: Path
) -> Path:
"""
生成 SRT 檔案
Args:
segments: 字幕段落列表
output_path: 輸出路徑
Returns:
實際輸出的檔案路徑
"""
...
@staticmethod
def format_timestamp(seconds: float) -> str:
"""
格式化時間戳記
Args:
seconds: 秒數
Returns:
SRT 格式時間 (HH:MM:SS,mmm)
"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
import typer from rich.console import Console from rich.progress import Progress, TaskID from pathlib import Path
app = typer.Typer( name="subtitle-creator", help="🎬 自動字幕生成工具 - 從影片到多語言字幕" ) console = Console()
@app.command() def run( input_path: Path = typer.Argument( ..., help="影片檔案或資料夾路徑" ), source_lang: str = typer.Option( "ja", "--lang", "-l", help="來源語言 (en/ja)" ), target_lang: str = typer.Option( "zh-tw", "--target", "-t", help="目標語言 (zh-tw/zh-cn)" ), output_dir: Path = typer.Option( None, "--output", "-o", help="輸出目錄 (預設為影片所在目錄)" ), style: str = typer.Option( "translation", "--style", "-s", help="字幕樣式 (source/translation/bilingual)" ), recursive: bool = typer.Option( True, "--recursive/--no-recursive", "-r/-R", help="是否遞迴掃描子目錄" ), gpu: int = typer.Option( 0, "--gpu", "-g", help="指定 GPU 裝置 ID" ), config_file: Path = typer.Option( None, "--config", "-c", help="配置檔路徑" ) ): """ 執行字幕生成流程
範例:
subtitle-creator run ./videos --lang ja --target zh-tw
subtitle-creator run movie.mp4 -l en -s bilingual
"""
...
@app.command() def transcribe( input_path: Path, lang: str = typer.Option("ja", "--lang", "-l"), output: Path = typer.Option(None, "--output", "-o") ): """僅執行語音辨識,輸出逐字稿""" ...
@app.command() def translate( input_srt: Path, source_lang: str = typer.Option("ja", "--from", "-f"), target_lang: str = typer.Option("zh-tw", "--to", "-t") ): """翻譯現有的 SRT 字幕檔""" ...
@app.command() def info(): """顯示系統資訊與模型狀態""" ...
if name == "main": app()
subtitle-creator/ │ ├── 📁 src/ │ └── 📁 subtitle_creator/ │ ├── init.py │ ├── main.py # Entry point │ ├── cli.py # Typer CLI 定義 │ ├── config.py # Pydantic 設定模型 │ │ │ ├── 📁 core/ # 核心模組 │ │ ├── init.py │ │ ├── scanner.py # 影片掃描 │ │ ├── extractor.py # 音軌擷取 │ │ ├── transcriber.py # 語音辨識 │ │ ├── translator.py # 翻譯引擎 │ │ └── writer.py # SRT 生成 │ │ │ ├── 📁 models/ # 資料模型 │ │ ├── init.py │ │ ├── segment.py │ │ ├── transcript.py │ │ └── video.py │ │ │ └── 📁 utils/ # 工具函式 │ ├── init.py │ ├── ffmpeg.py │ ├── gpu.py │ └── logger.py │ ├── 📁 tests/ # 測試 │ ├── init.py │ ├── conftest.py │ ├── test_scanner.py │ ├── test_extractor.py │ ├── test_transcriber.py │ ├── test_translator.py │ └── test_writer.py │ ├── 📁 docs/ # 文件 │ ├── getting-started.md │ ├── configuration.md │ └── api-reference.md │ ├── 📁 examples/ # 範例 │ ├── basic_usage.py │ └── batch_processing.py │ ├── 📄 pyproject.toml # 專案設定 (uv/poetry) ├── 📄 config.example.yaml # 範例配置檔 ├── 📄 README.md ├── 📄 LICENSE └── 📄 .gitignore
general:
work_dir: "./temp"
keep_intermediate: false
log_level: "INFO"
scanner:
formats: - ".mp4" - ".mkv" - ".ts" - ".avi" - ".mov" - ".webm" - ".flv"
recursive: true
exclude_patterns: - "_temp." - ".*"
extractor:
ffmpeg_path: ""
output: format: "wav" sample_rate: 16000 channels: 1 codec: "pcm_s16le"
audio_track: null
transcriber:
model_size: "large-v3"
device: "cuda"
compute_type: "float16"
language: null
batch_size: 16
vad: enabled: true # VAD 閾值 (0-1, 越高越嚴格) threshold: 0.5
alignment: enabled: true # 是否返回字元層級時間軸 return_char_alignments: false
translator:
model_name: "google/translategemma-12b"
device: "cuda"
load_in_4bit: true
load_in_8bit: false
max_length: 512
batch_size: 8
translation: # 來源語言 source_lang: "ja" # 目標語言 target_lang: "zh-tw"
writer:
style: "translation"
encoding: "utf-8-sig"
max_chars_per_line: 42
max_lines: 2
min_duration: 0.5
max_duration: 7.0
min_gap: 0.1
gpu:
device_id: 0
memory_limit: null
from pydantic import BaseModel, Field from pathlib import Path from typing import Literal
class GeneralConfig(BaseModel): work_dir: Path = Path("./temp") keep_intermediate: bool = False log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
class ScannerConfig(BaseModel): formats: list[str] = [".mp4", ".mkv", ".ts", ".avi", ".mov", ".webm", ".flv"] recursive: bool = True exclude_patterns: list[str] = ["_temp.", ".*"]
class ExtractorConfig(BaseModel): ffmpeg_path: str = "" sample_rate: int = 16000 channels: int = 1 audio_track: int | None = None
class TranscriberConfig(BaseModel): model_size: str = "large-v3" device: str = "cuda" compute_type: str = "float16" language: str | None = None batch_size: int = 16 vad_enabled: bool = True vad_threshold: float = 0.5
class TranslatorConfig(BaseModel): model_name: str = "google/translategemma-12b" device: str = "cuda" load_in_4bit: bool = True max_length: int = 512 batch_size: int = 8 source_lang: str = "ja" target_lang: str = "zh-tw"
class WriterConfig(BaseModel): style: Literal["source", "translation", "bilingual", "bilingual_reverse"] = "translation" encoding: str = "utf-8-sig" max_chars_per_line: int = 42 max_lines: int = 2
class Config(BaseModel): general: GeneralConfig = Field(default_factory=GeneralConfig) scanner: ScannerConfig = Field(default_factory=ScannerConfig) extractor: ExtractorConfig = Field(default_factory=ExtractorConfig) transcriber: TranscriberConfig = Field(default_factory=TranscriberConfig) translator: TranslatorConfig = Field(default_factory=TranslatorConfig) writer: WriterConfig = Field(default_factory=WriterConfig)
@classmethod
def from_yaml(cls, path: Path) -> "Config":
import yaml
with open(path) as f:
data = yaml.safe_load(f)
return cls(**data)
gantt title Subtitle Creator 開發時程 dateFormat YYYY-MM-DD section Phase 1: 基礎架構 專案初始化與環境設定 :p1_1, 2026-02-01, 3d 核心資料模型設計 :p1_2, after p1_1, 2d 配置系統實作 :p1_3, after p1_2, 2d CLI 框架搭建 :p1_4, after p1_3, 2d section Phase 2: 核心功能 影片掃描模組 :p2_1, after p1_4, 3d FFmpeg 音軌擷取 :p2_2, after p2_1, 4d WhisperX 整合 :p2_3, after p2_2, 5d 時間軸對齊驗證 :p2_4, after p2_3, 2d section Phase 3: 翻譯功能 TranslateGemma 整合 :p3_1, after p2_4, 5d 批次翻譯優化 :p3_2, after p3_1, 3d 翻譯品質調校 :p3_3, after p3_2, 3d section Phase 4: 輸出與優化 SRT 生成器 :p4_1, after p3_3, 3d 雙語字幕支援 :p4_2, after p4_1, 2d 效能優化 :p4_3, after p4_2, 4d section Phase 5: 測試與文件 單元測試 :p5_1, after p4_3, 5d 整合測試 :p5_2, after p5_1, 3d 文件撰寫 :p5_3, after p5_2, 3d 發布準備 :p5_4, after p5_3, 2d
| 任務 | 說明 | 交付物 |
|---|---|---|
| 專案初始化 | 建立專案結構、設定 uv、配置 linting | pyproject.toml, .pre-commit-config.yaml |
| 資料模型 | 定義 Segment, Transcript 等核心類別 | models/*.py |
| 配置系統 | Pydantic 設定模型、YAML 載入 | config.py, config.example.yaml |
| CLI 框架 | Typer 命令定義、Rich 進度條 | cli.py |
| 任務 | 說明 | 交付物 |
|---|---|---|
| 影片掃描 | 遞迴掃描、格式過濾、元資料擷取 | scanner.py |
| 音軌擷取 | FFmpeg 封裝、WAV 輸出、多音軌處理 | extractor.py |
| WhisperX 整合 | 模型載入、轉錄、VAD、對齊 | transcriber.py |
| 時間軸驗證 | 測試 word-level 準確度 | 測試報告 |
| 任務 | 說明 | 交付物 |
|---|---|---|
| TranslateGemma | 模型載入、4-bit 量化、推論封裝 | translator.py |
| 批次翻譯 | 多段落批次處理、記憶體優化 | 效能報告 |
| 品質調校 | 翻譯 prompt 優化、專有名詞處理 | 調校文件 |
| 任務 | 說明 | 交付物 |
|---|---|---|
| SRT 生成 | 時間格式化、編碼處理、行數限制 | writer.py |
| 雙語字幕 | 多種樣式支援 | 樣式範例 |
| 效能優化 | GPU 記憶體、批次處理、管線優化 | 效能報告 |
| 任務 | 說明 | 交付物 |
|---|---|---|
| 單元測試 | pytest 測試各模組 | tests/*.py |
| 整合測試 | 端對端測試流程 | CI 配置 |
| 文件 | API 文件、使用指南 | docs/*.md |
| 發布 | PyPI 打包、GitHub Release | v1.0.0 |
| 版本 | 預計日期 | 功能範圍 |
|---|---|---|
| v0.1.0 (Alpha) | 2026-02-21 | 基本轉錄功能 (影片→原文字幕) |
| v0.2.0 (Beta) | 2026-03-10 | 完整翻譯流程 (影片→翻譯字幕) |
| v0.3.0 (RC) | 2026-03-22 | 雙語字幕、效能優化 |
| v1.0.0 (Release) | 2026-03-31 | 正式發布、完整文件 |
[project] name = "subtitle-creator" version = "1.0.0" description = "🎬 自動字幕生成工具 - 從影片到多語言字幕" readme = "README.md" requires-python = ">=3.11" license = { text = "MIT" } authors = [ { name = "Your Name", email = "you@example.com" } ] keywords = ["subtitle", "whisper", "transcription", "translation", "srt"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Multimedia :: Sound/Audio :: Speech", "Topic :: Multimedia :: Video", ]
dependencies = [ # ===== Core ===== "torch>=2.2.0", "torchaudio>=2.2.0", "transformers>=4.40.0", "accelerate>=0.28.0", "bitsandbytes>=0.43.0", # 4-bit 量化
# ===== Speech Recognition =====
"whisperx>=3.1.0",
"faster-whisper>=1.0.0",
"ctranslate2>=4.0.0",
# ===== Audio/Video =====
"ffmpeg-python>=0.2.0",
"pydub>=0.25.1",
# ===== CLI & Utils =====
"typer>=0.12.0",
"rich>=13.7.0",
"pydantic>=2.6.0",
"pydantic-settings>=2.2.0",
"pyyaml>=6.0.1",
# ===== Logging =====
"loguru>=0.7.2",
]
[project.optional-dependencies] dev = [ "pytest>=8.0.0", "pytest-cov>=4.1.0", "pytest-asyncio>=0.23.0", "ruff>=0.3.0", "mypy>=1.8.0", "pre-commit>=3.6.0", ] docs = [ "mkdocs>=1.5.0", "mkdocs-material>=9.5.0", "mkdocstrings[python]>=0.24.0", ]
[project.scripts] subtitle-creator = "subtitle_creator.cli:app"
[build-system] requires = ["hatchling"] build-backend = "hatchling.build"
[tool.ruff] target-version = "py311" line-length = 100
[tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "SIM"]
[tool.mypy] python_version = "3.11" strict = true
[tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto"
| 依賴 | 版本 | 安裝方式 | 用途 |
|---|---|---|---|
| FFmpeg | ≥6.0 | 系統套件管理器 | 音訊擷取 |
| CUDA | ≥12.1 | NVIDIA 驅動 | GPU 加速 |
| cuDNN | ≥8.9 | NVIDIA 套件 | 深度學習加速 |
sudo apt update sudo apt install ffmpeg
brew install ffmpeg
下載 FFmpeg: https://ffmpeg.org/download.html
uv venv source .venv/bin/activate # Linux/macOS
uv pip install -e ".[dev]"
python -m venv .venv source .venv/bin/activate pip install -e ".[dev]"
subtitle-creator info
| 功能 | 優先級 | 說明 |
|---|---|---|
| Speaker Diarization | 高 | 多人對話識別,自動標註說話者 |
| ASS 字幕支援 | 中 | 支援更豐富的樣式格式 |
| 即時預覽 | 中 | TUI 介面即時顯示轉錄進度 |
| 斷點續傳 | 高 | 長時間任務中斷後可恢復 |
| 功能 | 說明 |
|---|---|
| Web UI | Flask/FastAPI 後端 + React 前端 |
| API 服務 | RESTful API 支援批次任務 |
| Docker 部署 | 容器化部署方案 |
| 多目標語言 | 擴展支援更多輸出語言 |
| 功能 | 說明 |
|---|---|
| 即時串流處理 | 直播字幕生成 |
| 自訂詞彙表 | 專有名詞/角色名稱對照 |
| 品質評估 | 自動評估翻譯品質分數 |
| 雲端整合 | AWS/GCP/Azure 部署支援 |
| 領域 | 關注技術 | 評估時程 |
|---|---|---|
| STT | Whisper large-v4 (若發布) | 持續關注 |
| 翻譯 | TranslateGemma 27B, NLLB-3.3B | Q2 2026 |
| 加速 | TensorRT-LLM, vLLM | Q2 2026 |
| 部署 | ONNX Runtime, OpenVINO | Q3 2026 |
| 類別 | 資源名稱 | 說明 | 連結 |
|---|---|---|---|
| 語音辨識 | WhisperX GitHub | 支援字詞級時間戳與說話者分離的語音辨識框架 | https://github.com/m-bain/whisperX |
| 語音辨識 | WhisperX 論文 | INTERSPEECH 2023 發表論文 | ArXiv Preprint |
| 翻譯模型 | TranslateGemma | Google 開源翻譯模型,支援多語言翻譯 | Google AI Blog |
| 翻譯模型 | Hugging Face Hub | TranslateGemma 模型下載與文件 | https://huggingface.co/google/translate-gemma |
| 多媒體處理 | FFmpeg 官方文件 | 完整的命令列工具與函式庫文件 | https://ffmpeg.org/documentation.html |
| 多媒體處理 | FFmpeg Wiki | 社群貢獻的使用指南與範例 | https://trac.ffmpeg.org/wiki |
| 字幕格式 | SRT 格式規範 | VideoLAN Wiki 上的 SubRip 格式說明 | https://wiki.videolan.org/SubRip |
| 對齊模型 | Wav2Vec 2.0 | Facebook/Meta 的語音表徵學習模型 | https://huggingface.co/facebook/wav2vec2-large-960h |
| 說話者分離 | pyannote-audio | 說話者分離與語音活動偵測框架 | https://github.com/pyannote/pyannote-audio |
| 術語 | 英文全稱 | 中文翻譯 | 說明 |
|---|---|---|---|
| ASR | Automatic Speech Recognition | 自動語音辨識 | 將人類語音轉換為文字的技術。本專案使用 WhisperX 作為核心 ASR 引擎,支援多語言辨識。 |
| VAD | Voice Activity Detection | 語音活動偵測 | 偵測音訊中是否存在人類語音的技術。用於過濾靜音片段,減少幻覺(hallucination)並提升辨識準確度。 |
| Speaker Diarization | Speaker Diarization | 說話者分離/標註 | 將音訊串流依據說話者身份進行分割的過程,輸出「誰在什麼時候說話」的資訊。本專案使用 pyannote-audio 實現。 |
| Forced Alignment | Forced Alignment | 強制對齊 | 將已知的文字轉錄與音訊錄音對齊,自動產生音素級別分割的過程。用於產生精確的字詞時間戳記。 |
| Phoneme-Based ASR | Phoneme-Based ASR | 基於音素的語音辨識 | 辨識語言中最小的語音單位(音素)的模型,例如 wav2vec2.0。用於實現精確的字詞級對齊。 |
| 術語 | 英文全稱 | 中文翻譯 | 說明 |
|---|---|---|---|
| SRT | SubRip Subtitle | SubRip 字幕格式 | 最廣泛使用的字幕格式之一,副檔名為 .srt。包含序號、時間碼和字幕文字,可嵌入 MKV 容器。 |
| Word-level Timestamps | Word-level Timestamps | 字詞級時間戳記 | 為轉錄文本中的每個單字/詞彙提供精確的開始與結束時間,而非僅段落級別的時間標記。 |
| Subtitle Frame | Subtitle Frame | 字幕幀 | SRT 格式中的單一字幕區塊,包含序號、時間範圍和顯示文字。 |
| 術語 | 英文全稱 | 中文翻譯 | 說明 |
|---|---|---|---|
| Batch Inference | Batch Inference | 批次推論 | 同時處理多個樣本的推論方式,可大幅提升處理速度。WhisperX 透過批次推論實現 70 倍即時速度。 |
| CUDA | Compute Unified Device Architecture | CUDA 統一計算架構 | NVIDIA 開發的平行運算平台與程式設計模型,用於 GPU 加速運算。本專案建議使用 CUDA 12.8。 |
| Transformer | Transformer | Transformer 模型 | 基於自注意力機制的神經網路架構,Whisper 和 TranslateGemma 均基於此架構。 |
| compute_type | Compute Type | 運算精度類型 | 模型推論時使用的數值精度,如 float16(較快)或 int8(記憶體友善但精度略低)。 |
| 術語 | 英文全稱 | 中文翻譯 | 說明 |
|---|---|---|---|
| Pipeline | Pipeline | 處理管線 | 將多個處理步驟串連成順序執行的工作流程,本專案包含掃描→提取→轉錄→翻譯→輸出五階段管線。 |
| Watchdog | Watchdog | 檔案監控 | 監控檔案系統變更的 Python 函式庫,用於實現動態資料夾監控功能。 |
| FFmpeg | Fast Forward MPEG | FFmpeg 多媒體框架 | 開源的多媒體處理框架,用於音訊提取、格式轉換等操作。 |
SRT(SubRip Subtitle)是最廣泛使用的字幕格式之一,採用純文字編碼,結構簡單易讀。每個字幕幀包含以下元素:
序號
開始時間 --> 結束時間
字幕文字(可多行)
[空白行作為分隔]
| 欄位 | 格式 | 說明 |
|---|---|---|
| 序號 | 正整數 n |
字幕的順序編號,從 1 開始遞增 |
| 開始時間 | hh:mm:ss,ms |
字幕顯示的起始時間點 |
| 結束時間 | hh:mm:ss,ms |
字幕隱藏的結束時間點 |
| 時間分隔符 | --> |
箭頭符號,前後各有一個空格 |
| 字幕文字 | UTF-8 文字 | 實際顯示的字幕內容,可包含多行 |
| 區塊分隔 | 空白行 | 每個字幕幀之間必須以空白行分隔 |
hh:mm:ss,ms
│ │ │ └── 毫秒(3位數,000-999)
│ │ └───── 秒(2位數,00-59)
│ └──────── 分(2位數,00-59)
└─────────── 時(2位數,00-99)
重要注意事項:
- 小數點分隔符使用逗號
,(法式風格),而非句點. - 毫秒必須是 3 位數,不足需補零
- 時間碼之間的箭頭
-->前後各需一個空格
1
00:00:05,320 --> 00:00:08,150
歡迎收看本次教學影片
我是你們的主持人
2
00:00:08,500 --> 00:00:12,800
今天我們將學習如何使用
自動字幕生成工具
3
00:00:13,100 --> 00:00:17,450
這個工具支援<b>多語言辨識</b>
以及<i>自動翻譯</i>功能
4
00:00:18,000 --> 00:00:22,300
讓我們開始吧!SRT 格式支援部分 HTML 標籤用於文字樣式控制:
| 標籤 | 效果 | 範例 |
|---|---|---|
<b>...</b> |
粗體 | <b>重要文字</b> |
<i>...</i> |
斜體 | <i>強調文字</i> |
<u>...</u> |
底線 | <u>標註文字</u> |
<s>...</s> |
<s>刪除文字</s> |
|
<font>...</font> |
字型屬性 | <font color="#FF0000">紅色文字</font> |
字型標籤屬性:
<font color="#RRGGBB" face="字型名稱" size="大小">文字內容</font>
⚠️ 相容性注意:並非所有播放器都支援 HTML 標籤,建議在需要最大相容性時避免使用樣式標籤。
# 提取為 16kHz 單聲道 WAV(WhisperX 最佳輸入格式)
ffmpeg -i input_video.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 output_audio.wav
# 參數說明:
# -i : 輸入檔案
# -vn : 不處理視訊串流(video none)
# -acodec : 音訊編碼器(pcm_s16le = 16-bit PCM)
# -ar 16000 : 取樣率 16kHz
# -ac 1 : 單聲道# 提取為高品質 MP3
ffmpeg -i input_video.mp4 -vn -acodec libmp3lame -ab 192k output_audio.mp3
# 參數說明:
# -acodec libmp3lame : 使用 LAME MP3 編碼器
# -ab 192k : 位元率 192 kbps# 直接複製音訊串流,不重新編碼
ffmpeg -i input_video.mp4 -vn -acodec copy output_audio.aacffmpeg -i input.mp3 -acodec pcm_s16le -ar 16000 -ac 1 output.wavffmpeg -i input.wav -acodec libmp3lame -ab 128k output.mp3# 將任意音訊轉換為 16kHz
ffmpeg -i input_audio.wav -ar 16000 output_16k.wavffmpeg -i stereo_input.wav -ac 1 mono_output.wavffprobe -v quiet -print_format json -show_format -show_streams input_video.mp4ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,channels,duration -of csv=p=0 input.mp4ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4#!/bin/bash
# 批次將資料夾中所有 MP4 轉換為 WAV
INPUT_DIR="./videos"
OUTPUT_DIR="./audio"
mkdir -p "$OUTPUT_DIR"
for video in "$INPUT_DIR"/*.mp4; do
filename=$(basename "$video" .mp4)
ffmpeg -i "$video" -vn -acodec pcm_s16le -ar 16000 -ac 1 "$OUTPUT_DIR/${filename}.wav" -y
echo "已處理: $filename"
done
echo "批次轉換完成!"# 批次將資料夾中所有 MP4 轉換為 WAV
$inputDir = ".\videos"
$outputDir = ".\audio"
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
Get-ChildItem -Path $inputDir -Filter "*.mp4" | ForEach-Object {
$outputFile = Join-Path $outputDir ($_.BaseName + ".wav")
ffmpeg -i $_.FullName -vn -acodec pcm_s16le -ar 16000 -ac 1 $outputFile -y
Write-Host "已處理: $($_.Name)"
}
Write-Host "批次轉換完成!"import subprocess
from pathlib import Path
def extract_audio(input_path: Path, output_path: Path) -> bool:
"""使用 FFmpeg 從影片提取音訊"""
cmd = [
"ffmpeg", "-i", str(input_path),
"-vn", # 不處理視訊
"-acodec", "pcm_s16le", # 16-bit PCM
"-ar", "16000", # 16kHz
"-ac", "1", # 單聲道
"-y", # 覆寫輸出
str(output_path)
]
result = subprocess.run(
cmd,
capture_output=True,
text=True
)
return result.returncode == 0import whisperx
import torch
# 設定運算裝置
device = "cuda" if torch.cuda.is_available() else "cpu"
compute_type = "float16" if device == "cuda" else "int8"
# 載入模型
model = whisperx.load_model(
"large-v2", # 模型名稱: tiny, base, small, medium, large-v2, large-v3
device=device, # 運算裝置: cuda 或 cpu
compute_type=compute_type, # 運算精度: float16, int8
language="zh" # 指定語言可提升準確度(可選)
)
# 載入音訊
audio = whisperx.load_audio("path/to/audio.wav")
# 執行轉錄
result = model.transcribe(
audio,
batch_size=16, # 批次大小,GPU 記憶體不足時可降低
language="zh" # 中文
)
# 輸出結果
print(result["segments"])
# [{'start': 0.0, 'end': 2.5, 'text': '歡迎收看本次教學影片'}, ...]import whisperx
# 假設已有轉錄結果 result
# 載入對齊模型(根據語言自動選擇)
model_a, metadata = whisperx.load_align_model(
language_code=result["language"], # 使用偵測到的語言
device=device
)
# 執行對齊
aligned_result = whisperx.align(
result["segments"], # 轉錄片段
model_a, # 對齊模型
metadata, # 模型元資料
audio, # 原始音訊
device, # 運算裝置
return_char_alignments=False # 是否返回字元級對齊
)
# 對齊後的結果包含字詞級時間戳
print(aligned_result["segments"])
# [{'start': 0.0, 'end': 2.5, 'text': '歡迎收看', 'words': [
# {'word': '歡迎', 'start': 0.0, 'end': 0.5},
# {'word': '收看', 'start': 0.6, 'end': 1.0},
# ...
# ]}, ...]
# 釋放 GPU 記憶體
import gc
gc.collect()
torch.cuda.empty_cache()
del model_afrom whisperx.diarize import DiarizationPipeline
# 需要 Hugging Face 存取權杖
# 請先在 huggingface.co 申請並同意以下模型的使用協議:
# - pyannote/segmentation-3.0
# - pyannote/speaker-diarization-3.1
HF_TOKEN = "your_huggingface_token"
# 載入說話者分離模型
diarize_model = DiarizationPipeline(
use_auth_token=HF_TOKEN,
device=device
)
# 執行說話者分離
diarize_segments = diarize_model(
audio,
min_speakers=2, # 最少說話者數量(可選)
max_speakers=5 # 最多說話者數量(可選)
)
# 將說話者標籤指派給轉錄結果
final_result = whisperx.assign_word_speakers(
diarize_segments,
aligned_result
)
# 結果現在包含說話者 ID
print(final_result["segments"])
# [{'start': 0.0, 'end': 2.5, 'text': '歡迎收看', 'speaker': 'SPEAKER_00'}, ...]"""
WhisperX 完整工作流程範例
包含:轉錄 → 對齊 → 說話者分離 → 輸出 SRT
"""
import whisperx
import torch
import gc
from whisperx.diarize import DiarizationPipeline
from pathlib import Path
class WhisperXPipeline:
"""WhisperX 處理管線封裝類別"""
def __init__(
self,
model_name: str = "large-v2",
device: str = None,
compute_type: str = None,
hf_token: str = None,
language: str = None
):
# 自動偵測裝置
self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
self.compute_type = compute_type or ("float16" if self.device == "cuda" else "int8")
self.hf_token = hf_token
self.language = language
# 載入 ASR 模型
print(f"載入 WhisperX 模型: {model_name} ({self.device})")
self.model = whisperx.load_model(
model_name,
self.device,
compute_type=self.compute_type,
language=self.language
)
# 延遲載入其他模型
self.align_model = None
self.diarize_model = None
def transcribe(self, audio_path: str, batch_size: int = 16) -> dict:
"""執行語音轉文字"""
audio = whisperx.load_audio(audio_path)
result = self.model.transcribe(
audio,
batch_size=batch_size,
language=self.language
)
return result, audio
def align(self, result: dict, audio) -> dict:
"""執行字詞級對齊"""
if self.align_model is None:
self.align_model, self.metadata = whisperx.load_align_model(
language_code=result["language"],
device=self.device
)
aligned = whisperx.align(
result["segments"],
self.align_model,
self.metadata,
audio,
self.device,
return_char_alignments=False
)
return aligned
def diarize(self, audio, aligned_result: dict, min_speakers: int = None, max_speakers: int = None) -> dict:
"""執行說話者分離"""
if self.hf_token is None:
raise ValueError("說話者分離需要 Hugging Face Token")
if self.diarize_model is None:
self.diarize_model = DiarizationPipeline(
use_auth_token=self.hf_token,
device=self.device
)
diarize_segments = self.diarize_model(
audio,
min_speakers=min_speakers,
max_speakers=max_speakers
)
final_result = whisperx.assign_word_speakers(diarize_segments, aligned_result)
return final_result
def process(
self,
audio_path: str,
enable_alignment: bool = True,
enable_diarization: bool = False,
**kwargs
) -> dict:
"""完整處理流程"""
# 步驟 1: 轉錄
result, audio = self.transcribe(audio_path, kwargs.get("batch_size", 16))
# 步驟 2: 對齊(可選)
if enable_alignment:
result = self.align(result, audio)
# 步驟 3: 說話者分離(可選)
if enable_diarization:
result = self.diarize(
audio,
result,
kwargs.get("min_speakers"),
kwargs.get("max_speakers")
)
return result
def cleanup(self):
"""釋放 GPU 記憶體"""
gc.collect()
if self.device == "cuda":
torch.cuda.empty_cache()
def segments_to_srt(segments: list, output_path: str):
"""將轉錄片段轉換為 SRT 字幕檔"""
with open(output_path, "w", encoding="utf-8") as f:
for i, segment in enumerate(segments, 1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
# 如果有說話者標籤,加入前綴
if "speaker" in segment:
text = f"[{segment['speaker']}] {text}"
f.write(f"{i}\n")
f.write(f"{start} --> {end}\n")
f.write(f"{text}\n\n")
def format_timestamp(seconds: float) -> str:
"""將秒數轉換為 SRT 時間格式"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
# === 使用範例 ===
if __name__ == "__main__":
# 初始化管線
pipeline = WhisperXPipeline(
model_name="large-v2",
language="zh",
hf_token="your_hf_token_here" # 如需說話者分離
)
# 處理音訊
result = pipeline.process(
audio_path="./audio/sample.wav",
enable_alignment=True,
enable_diarization=True,
batch_size=16,
min_speakers=2,
max_speakers=4
)
# 輸出 SRT
segments_to_srt(result["segments"], "./output/sample.srt")
# 清理資源
pipeline.cleanup()
print("處理完成!")| 模型名稱 | 參數量 | VRAM 需求 | 相對速度 | 建議用途 |
|---|---|---|---|---|
tiny |
39M | ~1 GB | 最快 | 快速測試 |
base |
74M | ~1 GB | 很快 | 輕量應用 |
small |
244M | ~2 GB | 快 | 平衡選擇 |
medium |
769M | ~5 GB | 中等 | 高品質需求 |
large-v2 |
1550M | ~10 GB | 較慢 | 最佳準確度 |
large-v3 |
1550M | ~10 GB | 較慢 | 最新版本 |
# ============================================================
# Subtitle Creator 配置檔
# 版本: 1.0.0
# ============================================================
# ------------------------------------------------------------
# 專案資訊
# ------------------------------------------------------------
project:
name: "subtitle-creator"
version: "1.0.0"
description: "自動化字幕生成系統"
# ------------------------------------------------------------
# 路徑設定
# ------------------------------------------------------------
paths:
# 輸入資料夾(支援監控模式)
input_dir: "./input"
# 輸出資料夾
output_dir: "./output"
# 暫存資料夾(存放提取的音訊)
temp_dir: "./temp"
# 日誌資料夾
log_dir: "./logs"
# 模型快取目錄
model_cache_dir: "~/.cache/subtitle-creator"
# ------------------------------------------------------------
# 檔案處理設定
# ------------------------------------------------------------
file_handling:
# 支援的影片格式
video_extensions:
- ".mp4"
- ".mkv"
- ".avi"
- ".mov"
- ".webm"
- ".flv"
# 支援的音訊格式
audio_extensions:
- ".wav"
- ".mp3"
- ".m4a"
- ".flac"
- ".ogg"
# 是否遞迴掃描子資料夾
recursive_scan: true
# 是否在處理完成後刪除暫存檔
cleanup_temp: true
# 是否覆寫已存在的輸出檔
overwrite_existing: false
# ------------------------------------------------------------
# WhisperX 語音辨識設定
# ------------------------------------------------------------
whisperx:
# 模型名稱: tiny, base, small, medium, large-v2, large-v3
model: "large-v2"
# 運算裝置: cuda, cpu, auto
device: "auto"
# 運算精度: float16, float32, int8
# float16 - GPU 推薦,速度快
# int8 - 記憶體友善,精度略低
compute_type: "float16"
# 批次大小(GPU 記憶體不足時降低此值)
batch_size: 16
# 來源語言(留空則自動偵測)
# 支援: zh, en, ja, ko, de, fr, es, it 等
language: null
# 是否啟用字詞級對齊
enable_alignment: true
# 是否啟用說話者分離
enable_diarization: false
# 說話者分離設定(僅在 enable_diarization: true 時生效)
diarization:
# Hugging Face 存取權杖
hf_token: "${HF_TOKEN}" # 建議使用環境變數
# 最少說話者數量
min_speakers: null
# 最多說話者數量
max_speakers: null
# ------------------------------------------------------------
# 翻譯設定
# ------------------------------------------------------------
translation:
# 是否啟用翻譯功能
enabled: true
# 翻譯模型: google/translate-gemma-12b, 或其他 HuggingFace 模型
model: "google/translate-gemma-12b"
# 來源語言(留空則使用 WhisperX 偵測結果)
source_lang: null
# 目標語言
target_lang: "zh-TW"
# 運算裝置
device: "auto"
# 批次大小
batch_size: 8
# 最大輸入長度(tokens)
max_length: 512
# ------------------------------------------------------------
# 輸出格式設定
# ------------------------------------------------------------
output:
# 輸出格式(可多選)
formats:
- "srt" # SubRip 字幕
# - "vtt" # WebVTT 字幕
# - "json" # JSON 格式(含完整元資料)
# - "txt" # 純文字(不含時間碼)
# SRT 格式設定
srt:
# 單行最大字元數(超過則自動換行)
max_line_length: 42
# 最大顯示行數
max_lines: 2
# 字幕最短顯示時間(秒)
min_duration: 0.5
# 字幕最長顯示時間(秒)
max_duration: 7.0
# 相鄰字幕最小間隔(秒)
min_gap: 0.1
# 檔案命名模式
# 可用變數: {name}, {lang}, {date}, {timestamp}
filename_pattern: "{name}_{lang}"
# 輸出編碼
encoding: "utf-8"
# 是否加入 BOM(某些播放器需要)
include_bom: false
# ------------------------------------------------------------
# 音訊提取設定(FFmpeg)
# ------------------------------------------------------------
audio_extraction:
# 輸出格式
format: "wav"
# 取樣率(Hz)- WhisperX 建議 16000
sample_rate: 16000
# 聲道數: 1(單聲道)或 2(立體聲)
channels: 1
# 位元深度: 16 或 24
bit_depth: 16
# FFmpeg 額外參數
extra_args: []
# ------------------------------------------------------------
# 效能設定
# ------------------------------------------------------------
performance:
# 並行處理數量(0 = 自動偵測 CPU 核心數)
num_workers: 0
# GPU 設定
gpu:
# 指定使用的 GPU ID(多 GPU 時)
device_id: 0
# 是否啟用 CUDA 記憶體池
memory_pool: true
# 是否啟用進度條
show_progress: true
# ------------------------------------------------------------
# 監控模式設定
# ------------------------------------------------------------
watch_mode:
# 是否啟用資料夾監控
enabled: false
# 監控間隔(秒)
interval: 5
# 檔案穩定時間(秒)- 確保檔案寫入完成
stability_threshold: 2
# ------------------------------------------------------------
# 日誌設定
# ------------------------------------------------------------
logging:
# 日誌等級: DEBUG, INFO, WARNING, ERROR
level: "INFO"
# 是否輸出到檔案
file_output: true
# 日誌檔案輪換
rotation:
# 單檔最大大小
max_size: "10MB"
# 保留檔案數量
backup_count: 5
# 日誌格式
format: "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"# ============================================================
# Subtitle Creator 環境變數
# 複製此檔案為 .env 並填入實際值
# ============================================================
# Hugging Face 存取權杖(說話者分離功能需要)
# 在 https://huggingface.co/settings/tokens 取得
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# CUDA 裝置設定(多 GPU 時指定)
CUDA_VISIBLE_DEVICES=0
# FFmpeg 執行檔路徑(如未加入 PATH)
# FFMPEG_PATH=/usr/local/bin/ffmpeg
# 模型快取目錄
# TRANSFORMERS_CACHE=/path/to/cache
# HF_HOME=/path/to/huggingface
# 代理設定(如需要)
# HTTP_PROXY=http://proxy.example.com:8080
# HTTPS_PROXY=http://proxy.example.com:8080"""配置檔載入工具"""
import os
from pathlib import Path
from typing import Any
import yaml
from pydantic import BaseModel, Field
from dotenv import load_dotenv
class WhisperXConfig(BaseModel):
"""WhisperX 配置模型"""
model: str = "large-v2"
device: str = "auto"
compute_type: str = "float16"
batch_size: int = 16
language: str | None = None
enable_alignment: bool = True
enable_diarization: bool = False
class TranslationConfig(BaseModel):
"""翻譯配置模型"""
enabled: bool = True
model: str = "google/translate-gemma-12b"
source_lang: str | None = None
target_lang: str = "zh-TW"
device: str = "auto"
batch_size: int = 8
class Config(BaseModel):
"""主配置模型"""
whisperx: WhisperXConfig = Field(default_factory=WhisperXConfig)
translation: TranslationConfig = Field(default_factory=TranslationConfig)
# ... 其他配置區塊
def load_config(config_path: str | Path = "config.yaml") -> Config:
"""載入並驗證配置檔"""
# 載入環境變數
load_dotenv()
config_path = Path(config_path)
if not config_path.exists():
raise FileNotFoundError(f"配置檔不存在: {config_path}")
with open(config_path, "r", encoding="utf-8") as f:
raw_config = yaml.safe_load(f)
# 展開環境變數引用(如 ${HF_TOKEN})
raw_config = _expand_env_vars(raw_config)
# 驗證並返回配置
return Config(**raw_config)
def _expand_env_vars(obj: Any) -> Any:
"""遞迴展開配置中的環境變數引用"""
if isinstance(obj, str):
if obj.startswith("${") and obj.endswith("}"):
env_var = obj[2:-1]
return os.getenv(env_var, "")
return obj
elif isinstance(obj, dict):
return {k: _expand_env_vars(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [_expand_env_vars(item) for item in obj]
return obj
# 使用範例
if __name__ == "__main__":
config = load_config("config.yaml")
print(f"WhisperX 模型: {config.whisperx.model}")
print(f"目標語言: {config.translation.target_lang}")| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0.0 | 2026-01-30 | 初始版本,完成核心功能規格定義 |
本文件由 Subtitle Creator 專案團隊維護