Spaces:
Sleeping
Sleeping
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() |