File size: 8,327 Bytes
8d68912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import os
import sys
import whisper
import requests
import subprocess
import math
from pydub import AudioSegment
import time

# 常量定義
MODEL_OPTIONS = ["tiny", "base", "small", "medium", "large", "large-v2", "large-v3"]
DEFAULT_MODEL = "small"
DEFAULT_PROMPT = "請轉錄以下內容為繁體中文"
MAX_FILE_SIZE_MB = 25  # OpenAI API 文件大小限制為 25MB

# 檢查 ffmpeg 是否已安裝
def is_ffmpeg_installed():
    try:
        subprocess.run(["ffmpeg", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
        return True
    except FileNotFoundError:
        return False

# 檢查文件大小
def check_file_size(file_path, max_size_mb=MAX_FILE_SIZE_MB):
    try:
        file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
        return file_size_mb <= max_size_mb, file_size_mb
    except Exception as e:
        print(f"檢查文件大小時出錯: {str(e)}")
        return True, 0

# 分割音頻文件為較小的片段
def split_audio_file(file_path, segment_duration_ms=30000):  # 默認 30 秒一段
    try:
        # 載入音頻
        print(f"正在載入音頻文件: {file_path}")
        audio = AudioSegment.from_file(file_path)
        duration_ms = len(audio)
        
        # 計算需要分割的段數
        segments_count = math.ceil(duration_ms / segment_duration_ms)
        print(f"音頻總長度: {duration_ms/1000:.1f} 秒,將分割為 {segments_count} 個片段")
        
        # 分割音頻
        file_name = os.path.splitext(file_path)[0]
        file_ext = os.path.splitext(file_path)[1]
        segment_files = []
        
        for i in range(segments_count):
            start_time = i * segment_duration_ms
            end_time = min((i + 1) * segment_duration_ms, duration_ms)
            segment = audio[start_time:end_time]
            
            segment_path = f"{file_name}_part{i+1}{file_ext}"
            print(f"正在導出片段 {i+1}/{segments_count}...")
            segment.export(segment_path, format=file_ext.replace(".", ""))
            segment_files.append(segment_path)
        
        return segment_files
    except Exception as e:
        print(f"分割音頻文件時出錯: {str(e)}")
        return [file_path]  # 發生錯誤時返回原始文件

# 使用本地 Whisper 模型進行轉錄,實時顯示結果
def transcribe_with_local_model(audio_path, model_size, prompt):
    if not is_ffmpeg_installed():
        print("錯誤: 找不到 ffmpeg。請安裝 ffmpeg 後再使用本地模型。")
        print("在 Mac 上,您可以使用 'brew install ffmpeg' 命令安裝。")
        return
    
    try:
        print(f"正在載入 {model_size} 模型...")
        model = whisper.load_model(model_size)
        
        # 分割音頻為較小的片段以實時顯示結果
        segments = split_audio_file(audio_path)
        
        full_text = ""
        for i, segment_path in enumerate(segments):
            print(f"\n處理片段 {i+1}/{len(segments)}...")
            
            # 使用 Whisper 模型轉錄當前片段
            result = model.transcribe(
                segment_path, 
                language="zh", 
                task="transcribe", 
                initial_prompt=prompt
            )
            
            # 顯示當前片段的轉錄結果
            segment_text = result["text"]
            print(f"片段 {i+1} 轉錄結果: {segment_text}")
            
            # 累積完整文本
            full_text += segment_text + " "
            
            # 如果是臨時創建的分割文件,則刪除
            if segment_path != audio_path:
                os.remove(segment_path)
        
        return full_text.strip()
    
    except Exception as e:
        print(f"使用本地模型轉錄時出錯: {str(e)}")
        return None

# 使用 OpenAI API 進行轉錄,實時顯示結果
def transcribe_with_openai_api(audio_path, api_key, prompt):
    # 檢查文件大小
    is_size_ok, file_size_mb = check_file_size(audio_path)
    if not is_size_ok:
        print(f"警告: 音頻文件大小 ({file_size_mb:.1f}MB) 超過 OpenAI API 限制 (25MB)。")
        print("將嘗試分割文件...")
    
    try:
        # 分割音頻為較小的片段
        segments = split_audio_file(audio_path)
        
        full_text = ""
        for i, segment_path in enumerate(segments):
            print(f"\n處理片段 {i+1}/{len(segments)}...")
            
            # 檢查當前片段的大小
            is_segment_size_ok, segment_size_mb = check_file_size(segment_path)
            if not is_segment_size_ok:
                print(f"警告: 片段 {i+1} 大小 ({segment_size_mb:.1f}MB) 超過限制,將跳過此片段。")
                continue
            
            # 使用 OpenAI API 轉錄當前片段
            headers = {
                "Authorization": f"Bearer {api_key}"
            }
            
            with open(segment_path, "rb") as f:
                files = {
                    "file": (os.path.basename(segment_path), f)
                }
                
                data = {
                    "model": "whisper-1",
                    "prompt": prompt
                }
                
                print(f"正在發送片段 {i+1} 到 OpenAI API...")
                response = requests.post(
                    "https://api.openai.com/v1/audio/transcriptions",
                    headers=headers,
                    files=files,
                    data=data
                )
            
            if response.status_code == 200:
                segment_text = response.json().get("text", "")
                print(f"片段 {i+1} 轉錄結果: {segment_text}")
                full_text += segment_text + " "
            else:
                print(f"片段 {i+1} 轉錄失敗: {response.status_code} - {response.text}")
            
            # 如果是臨時創建的分割文件,則刪除
            if segment_path != audio_path:
                os.remove(segment_path)
        
        return full_text.strip()
    
    except Exception as e:
        print(f"使用 OpenAI API 轉錄時出錯: {str(e)}")
        return None

# 主函數
def main():
    print("===== Whisper 語音轉錄 CLI 工具 =====")
    print("這個工具可以將音頻文件轉錄為文字,並在處理過程中實時顯示結果。")
    
    # 獲取音頻文件路徑
    audio_path = input("\n請輸入音頻文件路徑: ").strip()
    if not os.path.exists(audio_path):
        print(f"錯誤: 找不到文件 '{audio_path}'")
        return
    
    # 選擇使用本地模型還是 OpenAI API
    use_openai = input("\n是否使用 OpenAI API? (y/n,默認: n): ").strip().lower() == 'y'
    
    if use_openai:
        # 使用 OpenAI API
        api_key = input("\n請輸入您的 OpenAI API Key: ").strip()
        if not api_key:
            print("錯誤: API Key 不能為空")
            return
        
        prompt = input("\n請輸入轉錄提示 (默認: '請轉錄以下內容為繁體中文'): ").strip()
        if not prompt:
            prompt = DEFAULT_PROMPT
        
        print("\n開始使用 OpenAI API 進行轉錄...")
        full_text = transcribe_with_openai_api(audio_path, api_key, prompt)
        
    else:
        # 使用本地模型
        print("\n可用的模型: " + ", ".join(MODEL_OPTIONS))
        model_size = input(f"請選擇模型 (默認: {DEFAULT_MODEL}): ").strip()
        if not model_size or model_size not in MODEL_OPTIONS:
            model_size = DEFAULT_MODEL
        
        prompt = input("\n請輸入轉錄提示 (默認: '請轉錄以下內容為繁體中文'): ").strip()
        if not prompt:
            prompt = DEFAULT_PROMPT
        
        print("\n開始使用本地模型進行轉錄...")
        full_text = transcribe_with_local_model(audio_path, model_size, prompt)
    
    if full_text:
        print("\n===== 完整轉錄結果 =====")
        print(full_text)
        
        # 保存結果到文件
        output_path = os.path.splitext(audio_path)[0] + "_transcript.txt"
        with open(output_path, "w", encoding="utf-8") as f:
            f.write(full_text)
        print(f"\n轉錄結果已保存到: {output_path}")

if __name__ == "__main__":
    main()