1. 實現功能
M4-4: 校對增強版 (最終完全體)
本腳本是整個 Module 的最終形態,采用了“代碼預處理 + LLM校對”的終極方案:
- 代碼預處理: 確定性地在每個語音片段后添加逗號,生成一份“標點草稿”。
- LLM校對: LLM 的任務被簡化為“校對和修正”這份草稿,將部分逗號根據語境智能地升級為句號、問號等。
這是一種極其高效和可靠的 AI 工程實踐。
最終流程:
- 轉錄 -> 2. 預處理 -> 3. 代碼生成標點草稿 -> 4. LLM 校對修正 -> 5. 摘要提取 -> 6. 保存
2.運行效果
(whisper) root@DESKTOP-8IU6393:/home/gpu3090# /root/anaconda3/envs/whisper/bin/python /home/gpu3090/vscode/M4-實用技巧/M4-4-摘要提取.py
🚀 開始 Whisper 語音轉錄與智能后處理流程
======================================================================
🔧 步驟 1: 正在加載 Whisper 模型 'large-v3'...
? 模型加載完成。耗時: 0:00:13.904675🎙? 步驟 2: 正在轉錄音頻文件...
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 17888/17888 [00:24<00:00, 717.09frames/s]
? 音頻轉錄完成。耗時: 0:00:25.528546📝 原始轉錄文本 (無標點拼接):
--------------------------------------------------
作品二十六號我們家的后園有半畝空地母親說,讓她慌著怪可惜的你們那么愛吃花生,就開辟出來種花生吧我們姐弟幾個都很高興埋種、翻地、播種、澆水沒過幾個月,居然收獲了母親說,今晚我們過一個收獲節請你們父親也來嘗嘗我們的新花生,好不好我們都說好母親把花生做成了好幾樣食品還吩咐就在后園的茅亭里過這個節晚上天色不太好可是父親也來了實在很難得父親說,你們愛吃花生嗎我們爭著答應,愛誰能把花生的好處說出來姐姐說,花生的味兒美哥哥說,花生可以榨油我說,花生的價錢便宜誰都可以買來吃,都喜歡吃這就是它的好處父親說,花生的好處很多有一樣最可貴它的果實埋在地里不像桃子、石榴、蘋果那樣把鮮紅嫩綠的果實高高地掛在枝頭上使人一見就生愛慕之心你們看它矮矮地長在地上等到成熟了就會吃了也不能立刻分辨出來它有沒有果實必須挖出來才知道我們都說是母親也點點頭父親接下去說所以你們要像花生它雖然不好看可是很有用不是外表好看而沒有實用的東西我說那么人要做有用的人不是外表好看不要做只講體面而對別人沒有好處的人了父親說對這是我對你們的希望我們談到夜深才散花生做的食品都吃完了父親的話卻深深地印在我的心上歡迎光臨普通話學習網3w.png.compthxx.com
--------------------------------------------------🔄 步驟 3 & 4: 正在進行熱詞替換與中英文空格處理...
? 預處理完成。耗時: 0:00:00.003052?? 步驟 5.1: 正在由代碼生成“標點草稿”...
作品二十六號,我們家的后園有半畝空地,母親說,讓她慌著怪可惜的,你們那么愛吃花生,就開辟出來種花生吧,我們姐弟幾個都很高興,埋種、翻地、播種、澆水,沒過幾個月,居然收獲了,母親說,今晚我們過一個收獲節,請你們父親也來嘗嘗我們的新花生,好不好,我們都說好,母親把花生做成了好幾樣食品,還吩咐就在后園的茅亭里過這個節,晚上天色不太好,可是父親也來了,實在很難得,父親說,你們愛吃花生嗎,我們爭著答應,愛,誰能把花生的好處說出來,姐姐說,花生的味兒美,哥哥說,花生可以榨油,我說,花生的價錢便宜,誰都可以買來吃,都喜歡吃,這就是它的好處,父親說,花生的好處很多,有一樣最可貴,它的果實埋在地里,不像桃子、石榴、蘋果那樣,把鮮紅嫩綠的果實高高地掛在枝頭上,使人一見就生愛慕之心,你們看它矮矮地長在地上,等到成熟了,就會吃了,也不能立刻分辨出來,它有沒有果實,必須挖出來才知道,我們都說是,母親也點點頭,父親接下去說,所以你們要像花生,它雖然不好看,可是很有用,不是外表好看,而沒有實用的東西,我說,那么人要做有用的人,不是外表好看,不要做只講體面,而對別人沒有好處的人了,父親說,對,這是我對你們的希望,我們談到夜深才散,花生做的食品都吃完了,父親的話卻深深地印在我的心上,歡迎光臨普通話學習網,3w.png.com,pthxx.com,
? “標點草稿”生成完成。🤖 步驟 5.2: 正在調用 LLM 'qwen3:14b' 進行校對修正...🔄 正在發送 [校對] 請求到 Ollama...? Ollama [校對] 完成。
? LLM 校對處理完成。耗時: 0:02:38.052278? 最終全文 (經校對):
==================================================
<think></think>作品二十六號,我們家的后園有半畝空地,母親說:“讓它荒著怪可惜的,你們那么愛吃花生,就開辟出來種花生吧。”我們姐弟幾個都很高興,埋種、翻地、播種、澆水,沒過幾個月,居然收獲了。母親說:“今晚我們過一個收獲節,請你們父親也來嘗嘗我們的新花生,好不好?”我們都說好,母親把花生做成了好幾樣食品,還吩咐就在后園的茅亭里過這個節。晚上天色不太好,可是父親也來了,實在很難得。父親說:“你們愛吃花生嗎?”我們爭著答應:“愛。”“誰能把花生的好處說出來?”姐姐說:“花生的味兒美。”哥哥說:“花生可以榨油。”我說:“花生的價錢便宜,誰都可以買來吃,都喜歡吃,這就是它的好處。”父親說:“花生的好處很多,有一樣最可貴。它的果實埋在地里,不像桃子、石榴、蘋果那樣,把鮮紅嫩綠的果實高高地掛在枝頭上,使人一見就生愛慕之心。你們看它矮矮地長在地上,等到成熟了,就會吃掉,也不能立刻分辨出來,它有沒有果實,必須挖出來才知道。”我們都說是,母親也點點頭。父親接下去說:“所以你們要像花生,它雖然不好看,可是很有用。不是外表好看,而沒有實用的東西。”我說:“那么,人要做有用的人,不要做只講體面,而對別人沒有好處的人了。”父親說:“對,這是我對你們的希望。”我們談到夜深才散,花生做的食品都吃完了,父親的話卻深深地印在我的心上。歡迎光臨普通話學習網,3w.png.com,pthxx.com。
==================================================📜 步驟 6: 正在調用 LLM 'qwen3:14b' 進行摘要提取...🔄 正在發送 [摘要] 請求到 Ollama...
? LLM 摘要提取完成。耗時: 0:01:04.397725💡 文本摘要:
**************************************************
<think></think>- 文章通過一家人種花生、收獲花生并舉行收獲節的故事,傳達了父親對子女的教育和期望。
- 父親指出花生雖然外表不顯眼,但果實埋在地下,具有實用價值,借此教導子女要做對他人有用的人,而非只注重外表。
- 故事強調了內在價值的重要性,以及做人應注重實際貢獻,而非只追求表面的體面。
- 父親的教誨給“我”留下了深刻印象,成為文章的核心思想。
**************************************************💾 步驟 7: 正在保存所有結果文件...
📁 文件已保存: /home/gpu3090/26_final_text.txt
📁 文件已保存: /home/gpu3090/26_summary.txt
📁 文件已保存: /home/gpu3090/26_original.txt
? 文件保存完成。耗時: 0:00:00.000483🎉 全部流程完成!
======================================================================
?? 各步驟耗時統計:步驟1 (模型加載): 0:00:13.904675步驟2 (音頻轉錄): 0:00:25.528546步驟3&4 (預處理): 0:00:00.003052步驟5 (校對流程): 0:02:38.052278步驟6 (LLM摘要): 0:01:04.397725步驟7 (文件保存): 0:00:00.000483? 總計耗時: 0:04:21.919559📁 輸出文件:- 最終全文: ./26_final_text.txt- 文本摘要: ./26_summary.txt- 原始文本: ./26_original.txt
======================================================================
(whisper) root@DESKTOP-8IU6393:/home/gpu3090#
3.實現過程
3.1 搭建環境
3.2 代碼
import os
import whisper
import datetime
import torch
import re
import ollama# --- 1. 文本處理函數 ---
# (apply_replacements 和 add_spaces_around_english 無變化)
def apply_replacements(text, replacement_map):for old_word, new_word in replacement_map.items():text = text.replace(old_word, new_word)return textdef add_spaces_around_english(text):pattern1 = re.compile(r'([\u4e00-\u9fa5])([a-zA-Z0-9]+)')pattern2 = re.compile(r'([a-zA-Z0-9]+)([\u4e00-\u9fa5])')text = pattern1.sub(r'\1 \2', text)text = pattern2.sub(r'\1 \2', text)return text# <--- 重點:全新的 LLM 校對函數 ---
def correct_punctuation_with_llm(text_with_commas, llm_model_name):"""接收一份由逗號預處理過的“標點草稿”,讓 LLM 進行校對和修正。"""if not text_with_commas.strip():return ""prompt = f"""# 角色
你是一位經驗豐富的中文總編審,擅長精修文稿。# 任務
你收到了一份由初級助理處理過的文稿。助理已在每個自然的語音停頓處插入了【逗號】,形成了一份“標點草稿”。
你的任務是【審校并修正】這份草稿,將一些不恰當的逗號,根據上下文語境和句子完整性,修正為更合適的【句號】或【問號】等。# 指導原則
1. **修正為主**: 你的主要工作是判斷哪些逗號應該被“升級”為句號。
2. **保留為輔**: 如果一個逗號位于一個長句的中間,用于自然的停頓,那么它應該被保留。
3. **忠實內容**: 絕對不允許增刪或改動原文的任何字詞。# 學習示例 (Examples)
* **示例 1 (需要修正)*** **標點草稿**: 大家好我是小明,今天我們來聊聊人工智能,* **期望輸出**: 大家好,我是小明。今天我們來聊聊人工智能。* **示例 2 (部分修正,部分保留)*** **標點草稿**: 這個項目基于Python語言,使用了多種先進的算法模型,效果非常出色,* **期望輸出**: 這個項目基于Python語言,使用了多種先進的算法模型,效果非常出色。# 待校對的標點草稿
---
{text_with_commas}
---# 輸出要求
請直接輸出經過你最終審校和修正后的完美文稿。
/no_think
"""try:print(" 🔄 正在發送 [校對] 請求到 Ollama...")response = ollama.chat(model=llm_model_name, messages=[{'role': 'user', 'content': prompt}],options={'temperature': 0.4} # 校對任務需要一定的上下文理解,但不能太隨意)corrected_text = response['message']['content'].strip()print(" ? Ollama [校對] 完成。")return corrected_textexcept Exception as e:print(f"? 調用 Ollama API [校對] 時出錯: {e}")# 出錯時,返回至少保證斷句的草稿return text_with_commas# (extract_summary_with_llm 和 save_text_file 無變化)
def extract_summary_with_llm(full_text, llm_model_name):# ... (無變化)if not full_text.strip(): return "原文為空,無法生成摘要。"prompt = f"""# 角色
你是一位專業的內容分析師和摘要專家。
# 任務
你的任務是為以下完整的文稿,提取核心要點,并生成一段簡潔、流暢、重點突出的摘要。
# 指導原則
1. **長度控制**: 摘要的長度應保持在150到300字之間,精準概括,避免冗長。
2. **內容覆蓋**: 摘要應覆蓋文稿的主要話題、關鍵概念和最終結論。
3. **格式靈活**: 你可以使用無序列表(使用-或*)來呈現要點,也可以直接使用通順的段落形式。
4. **保持中立**: 摘要應客觀反映原文內容,不添加個人觀點。
# 待處理文稿
---
{full_text}
---
# 輸出要求
請直接輸出摘要內容,不要包含任何額外的前綴,如“這是摘要:”。
/no_think
"""try:print(" 🔄 正在發送 [摘要] 請求到 Ollama...")response = ollama.chat(model=llm_model_name, messages=[{'role': 'user', 'content': prompt}],options={'temperature': 0.3})return response['message']['content'].strip()except Exception as e:return f"摘要生成失敗: {e}"def save_text_file(text, output_path):# ... (無變化)with open(output_path, 'w', encoding='utf-8') as txt_file:txt_file.write(text)print(f"📁 文件已保存: {os.path.abspath(output_path)}")# --- 2. 主流程函數 ---
def run_transcription_pipeline(model_name, media_path, language, replacement_map, llm_model_name):"""執行完整的轉錄、后處理和摘要提取流程 (v7.0 - 校對增強版)。"""print("🚀 開始 Whisper 語音轉錄與智能后處理流程")print("=" * 70)total_start_time = datetime.datetime.now()# ... (前面步驟的初始化無變化)script_dir = os.path.dirname(os.path.abspath(__file__)) if '__file__' in locals() else '.'output_dir = script_dirdevice = "cuda" if torch.cuda.is_available() else "cpu"# 步驟 1 & 2: 加載模型與轉錄 (無變化)# ... (代碼省略,與前一版相同)step1_start = datetime.datetime.now()print(f"🔧 步驟 1: 正在加載 Whisper 模型 '{model_name}'...")model = whisper.load_model(model_name, device=device)step1_end = datetime.datetime.now(); step1_duration = step1_end - step1_startprint(f"? 模型加載完成。耗時: {step1_duration}\n")step2_start = datetime.datetime.now()print(f"🎙? 步驟 2: 正在轉錄音頻文件...")result = model.transcribe(media_path, language=language, verbose=False)original_segments = result['segments']original_text_display = result['text'].strip()step2_end = datetime.datetime.now(); step2_duration = step2_end - step2_startprint(f"? 音頻轉錄完成。耗時: {step2_duration}")print("\n📝 原始轉錄文本 (無標點拼接):\n" + "-"*50 + f"\n{original_text_display}\n" + "-"*50 + "\n")# 步驟 3 & 4: 預處理 (無變化)# ... (代碼省略,與前一版相同)step3_4_start = datetime.datetime.now()print("🔄 步驟 3 & 4: 正在進行熱詞替換與中英文空格處理...")processed_segments = []for segment in original_segments:new_segment = segment.copy()text = apply_replacements(new_segment['text'], replacement_map)text = add_spaces_around_english(text)new_segment['text'] = textprocessed_segments.append(new_segment)step3_4_end = datetime.datetime.now(); step3_4_duration = step3_4_end - step3_4_startprint(f"? 預處理完成。耗時: {step3_4_duration}\n")# <--- 重點修改:全新的步驟5 ---# 步驟 5.1: 代碼生成標點草稿step5_start = datetime.datetime.now()print("?? 步驟 5.1: 正在由代碼生成“標點草稿”...")texts_to_join = [s['text'].strip() for s in processed_segments if s['text'].strip()]text_with_commas = ",".join(texts_to_join) + "," # 在末尾也加上逗號,讓LLM決定最后用什么標點print(text_with_commas)print("? “標點草稿”生成完成。\n")# 步驟 5.2: LLM 校對修正print(f"🤖 步驟 5.2: 正在調用 LLM '{llm_model_name}' 進行校對修正...")final_text = correct_punctuation_with_llm(text_with_commas, llm_model_name)step5_end = datetime.datetime.now()step5_duration = step5_end - step5_start # 整個步驟5的耗時print(f"? LLM 校對處理完成。耗時: {step5_duration}\n")print("? 最終全文 (經校對):\n" + "="*50 + f"\n{final_text}\n" + "="*50 + "\n")# <--- 修改結束 ---# 后續步驟無變化# 步驟 6: LLM 摘要提取step6_start = datetime.datetime.now()print(f"📜 步驟 6: 正在調用 LLM '{llm_model_name}' 進行摘要提取...")summary_text = extract_summary_with_llm(final_text, llm_model_name)step6_end = datetime.datetime.now(); step6_duration = step6_end - step6_startprint(f"? LLM 摘要提取完成。耗時: {step6_duration}\n")print("💡 文本摘要:\n" + "*"*50 + f"\n{summary_text}\n" + "*"*50 + "\n")# 步驟 7: 保存文件# ... (代碼省略,與前一版相同)step7_start = datetime.datetime.now()print("💾 步驟 7: 正在保存所有結果文件...")base_name = os.path.splitext(os.path.basename(media_path))[0]final_txt_path = os.path.join(output_dir, f"{base_name}_final_text.txt")save_text_file(final_text, final_txt_path)summary_txt_path = os.path.join(output_dir, f"{base_name}_summary.txt")save_text_file(summary_text, summary_txt_path)original_txt_path = os.path.join(output_dir, f"{base_name}_original.txt")save_text_file(original_text_display, original_txt_path)step7_end = datetime.datetime.now(); step7_duration = step7_end - step7_startprint(f"? 文件保存完成。耗時: {step7_duration}\n")# 總結統計# ... (代碼省略,與前一版相同)total_end_time = datetime.datetime.now()total_duration = total_end_time - total_start_timeprint("🎉 全部流程完成!")print("=" * 70)print("?? 各步驟耗時統計:")print(f" 步驟1 (模型加載): {step1_duration}")print(f" 步驟2 (音頻轉錄): {step2_duration}")print(f" 步驟3&4 (預處理): {step3_4_duration}")print(f" 步驟5 (校對流程): {step5_duration}")print(f" 步驟6 (LLM摘要): {step6_duration}")print(f" 步驟7 (文件保存): {step7_duration}")print(f" ? 總計耗時: {total_duration}")print("\n📁 輸出文件:")print(f" - 最終全文: {final_txt_path}")print(f" - 文本摘要: {summary_txt_path}")print(f" - 原始文本: {original_txt_path}")print("=" * 70)# --- 3. 配置與執行 ---
if __name__ == "__main__":# ... (無變化)REPLACEMENT_MAP = {"烏班圖": "Ubuntu", "吉特哈布": "GitHub", "Moddy Talk": "MultiTalk","馬克道": "Markdown", "VS 扣德": "VS Code", "派森": "Python",}WHISPER_MODEL_NAME = "large-v3"MEDIA_FILE = "../audio/26.mp3"LANGUAGE = "zh"LLM_MODEL_NAME = "qwen3:14b"script_dir = os.path.dirname(os.path.abspath(__file__)) if '__file__' in locals() else '.'media_path_full = os.path.join(script_dir, MEDIA_FILE)if not os.path.exists(media_path_full):print(f"? 錯誤: 音頻文件不存在 -> {media_path_full}")else:run_transcription_pipeline(model_name=WHISPER_MODEL_NAME, media_path=media_path_full,language=LANGUAGE, replacement_map=REPLACEMENT_MAP,llm_model_name=LLM_MODEL_NAME)