本地WSL部署接入 whisper + ollama qwen3:14b 总结字幕校对增强版
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)