import env # 加载 .env 到环境变量 import os import dashscope import pydub from pydub import AudioSegment from pydub.silence import split_on_silence from dashscope.audio.asr import Recognition from dashscope import Generation from http import HTTPStatus from mysqlHandle import MySQLDB import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # 设置环境变量 # os.environ["DASHSCOPE_API_KEY"] = "sk-your-dashscope-key" def convert_mp3_to_wav(mp3_path, output_wav_path): """ 将MP3文件转换为16kHz单声道WAV格式,这是Qwen3-ASR-Flash模型的推荐格式 参数: mp3_path (str): MP3文件路径 output_wav_path (str): 输出WAV文件路径 返回值: str: 转换后的WAV文件路径 """ logger.info(f"开始转换MP3到WAV: {mp3_path}") # 加载MP3文件 audio = AudioSegment.from_file(mp3_path, format="mp3") # 转换为16kHz采样率、单声道、16位深度 audio = audio.set_frame_rate(16000).set_channels(1) # 导出为WAV格式 audio.export(output_wav_path, format="wav") logger.info(f"✓ MP3转换完成: {output_wav_path}") #print(f"✓ MP3转换完成: {output_wav_path}") return output_wav_path def split_audio_by_fixed_duration(audio_path, chunk_duration, output_folder): """ 将音频文件按固定时长分割成多个片段 参数: audio_path (str): 音频文件路径 chunk_duration (int): 分片时长(毫秒) output_folder (str): 输出文件夹路径 返回值: list: 分片文件路径列表 """ # 加载音频文件 audio = AudioSegment.from_file(audio_path) # 计算总时长(毫秒) total_duration = len(audio) # 分片数 num_chunks = total_duration // chunk_duration + 1 # 存储分片文件路径 chunks = [] # 创建输出文件夹 os.makedirs(output_folder, exist_ok=True) logger.info(f"开始音频分割,总时长: {total_duration/1000:.1f}秒,将分割为{num_chunks}个片段") for i in range(num_chunks): # 计算当前分片的起始和结束时间 start_time = i * chunk_duration end_time = (i + 1) * chunk_duration # 提取分片音频 chunk = audio[start_time:end_time] # 生成文件名 chunk_name = f"chunk_{i}.wav" chunk_path = os.path.join(output_folder, chunk_name) # 导出分片音频 chunk.export(chunk_path, format="wav") chunks.append(chunk_path) # 打印处理进度 progress = (i + 1) / num_chunks * 100 logger.info(f"✓ 已完成分片 {i+1}/{num_chunks} ({progress:.1f}%)") logger.info(f"✓ 音频分割完成,共生成{len(chunks)}个分片文件") return chunks def split_audio_by_smart_silence(audio_path, min_silence_len, silence_thresh, output_folder): """ 将音频文件按智能静音检测方式分割成多个片段,每段不超过3分钟 参数: audio_path (str): 音频文件路径 min_silence_len (int): 最小静音长度(毫秒) silence_thresh (int): 静音阈值(dBFS) output_folder (str): 输出文件夹路径 返回值: list: 分片文件路径列表 """ # 加载音频文件 audio = AudioSegment.from_file(audio_path, format="wav") # 按静音分割 segments = split_on_silence( audio, # 静音超过700毫秒则分割 min_silence_len=min_silence_len, # 静音阈值为-40dBFS silence_thresh=silence_thresh, # 保留静音部分 keep_silence=400 ) logger.info(f"✓ 静音分割完成,共{len(segments)}个初始片段") # 合并过短的片段 merged_segments = [] current_segment = None for segment in segments: if current_segment is None: current_segment = segment else: # 合并当前片段和新片段 temp_segment = current_segment + segment # 如果合并后的片段超过3分钟,则单独保存当前片段 if len(temp_segment) > 180000: # 3分钟=180,000毫秒 merged_segments.append(current_segment) current_segment = segment else: current_segment = temp_segment # 添加最后一个片段 if current_segment is not None: merged_segments.append(current_segment) logger.info(f"✓ 片段合并完成,共{len(merged_segments)}个最终片段") # 存储分片文件路径 chunks = [] # 创建输出文件夹 os.makedirs(output_folder, exist_ok=True) logger.info(f"开始导出音频片段到: {output_folder}") for i, segment in enumerate(merged_segments): # 生成文件名 chunk_name = f"chunk_{i}.wav" chunk_path = os.path.join(output_folder, chunk_name) # 导出分片音频 segment.export(chunk_path, format="wav") chunks.append(chunk_path) # 打印处理进度 progress = (i + 1) / len(merged_segments) * 100 logger.info(f"✓ 已完成分片 {i+1}/{len(merged_segments)} ({progress:.1f}%)") logger.info(f"✓ 智能静音分割完成,共生成{len(chunks)}个分片文件") return chunks def transcribe_audio(audio_path): """ 使用Paraformer实时语音识别模型(通过本地文件)转录音频文件 参数: audio_path (str): 音频文件路径(必须是16kHz单声道WAV) 返回值: str: 识别文本,如果失败返回空字符串 """ try: # 确保音频文件存在 if not os.path.exists(audio_path): logger.error(f"音频文件不存在: {audio_path}") return ['', ''] dashscope.api_key = os.getenv('DASHSCOPE_API_KEY', '') # 创建识别对象 recognition = Recognition( model=os.getenv('DASHSCOPE_ASR_MODEL', 'paraformer-realtime-v2'), format='wav', sample_rate=16000, language_hints=['zh','en'], # 中文和英文 callback=None ) # 调用识别 logger.info(f"开始识别音频: {audio_path}") result = recognition.call(audio_path) text=[] if result.status_code == HTTPStatus.OK: # 提取识别结果 logger.info(f"✓ {audio_path} 识别成功") sentence = result.get_sentence() text.append(merge_transcripts(sentence)) logger.info(f"识别文本长度: {len(text[0])}") text.append(analyze_and_correct_text(text[0])) return text else: logger.error(f"❌ 任务失败: {result.message}") return ['', ''] except Exception as e: logger.error(f"识别过程中发生异常: {e}") return ['', ''] def merge_transcripts(transcripts): """ 将多段识别文本合并成完整句子(保留原始段落逻辑,用空格连接) 参数: transcripts (list): 识别结果列表,每个元素为字典{'text': '识别文本'} 返回: str: 合并后的完整文本 """ # 输入参数检查 if not transcripts: return "" # 确保transcripts是可迭代对象 if not hasattr(transcripts, '__iter__'): return "" try: # 提取所有有效的text字段 texts = [] for t in transcripts: try: # 检查是否为字典类型且包含text字段 if isinstance(t, dict) and 'text' in t and t['text']: text = t['text'] # 确保text是字符串类型 if isinstance(text, str) and text.strip(): texts.append(text.strip()) except (KeyError, TypeError, AttributeError): # 忽略单个元素的处理错误,继续处理其他元素 continue # 用空格连接所有段落(根据实际需求可调整连接符) return " ".join(texts) if texts else "" except Exception as e: logger.error(f"合并转录文本时发生错误: {e}") return "" def text_correction(text): """ 使用通义千问模型修正文本中的错误和标点符号 参数: text (str): 需要修正的文本 返回值: str: 修正后的文本 """ logger.info("开始文本修正...") # 构建修正提示词 correction_prompt = """请仔细检查以下文本,修正其中的错误: 1. 错别字和语法错误 2. 标点符号使用错误 3. 语句不通顺的地方 4. 逻辑不清晰的部分 请直接返回修正后的完整文本,不要添加任何解释说明。""" # 构建消息列表 messages = [ {"role": "system", "content": "你是一个专业的文本校对助手,擅长修正文本中的各种错误。"}, {"role": "user", "content": correction_prompt}, {"role": "user", "content": text} ] logger.info("调用通义千问模型进行文本修正...") # 调用DashScope文本生成接口 response = Generation.call( model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), messages=messages, max_tokens=30000, temperature=0.1, # 使用较低的温度以提高确定性 top_p=0.5 ) # 检查API调用是否成功 if response.status_code != 200: logger.warning(f"❌ 文本修正API调用失败: {response.message}") raise Exception(f"文本修正API调用失败: {response.message}") logger.info("✓ 文本修正完成") # 返回修正后的文本 return response.output.text def analyze_and_correct_text(text): """ 分析文本并自动修正错误 参数: text (str): 待分析和修正的文本 prompt (str): 分析提示词 返回值: tuple: (修正后的文本, 分析结果) """ logger.info("开始文本分析和修正流程...") # 首先修正文本错误 corrected_text = text_correction(text) if corrected_text is None: logger.warning("文本修正返回 None,使用原始文本") corrected_text = text logger.info(f"原始文本长度: {len(text)}") logger.info(f"修正后文本长度: {len(corrected_text)}") # 使用修正后的文本进行分析 # analysis_result = analyze_text(corrected_text, prompt) return corrected_text def analyze_text(text, prompt): """ 使用通义千问模型分析文本 参数: text (str): 待分析文本 prompt (str): 分析提示词 返回值: str: 分析结果 """ logger.info("开始文本分析...") # 设置系统提示 system_prompt = "你是一个专业的文本分析助手,擅长根据提示词对长文本进行深入分析。" # 构建消息列表 messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}, {"role": "user", "content": text} ] logger.info("调用通义千问模型进行文本分析...") # 调用DashScope文本生成接口 response = Generation.call( model=os.getenv('DASHSCOPE_LLM_MODEL', 'qwen-plus'), messages=messages, max_tokens=8190, # 控制生成文本的最大长度 temperature=0.3, # 控制生成文本的确定性 top_p=0.7 # 控制生成文本的多样性 ) # 检查API调用是否成功 if response.status_code != 200: logger.error(f"❌ API调用失败: {response.message}") raise Exception(f"API调用失败: {response.message}") logger.info("✓ 文本分析完成") # 返回分析结果 return response.output.text def process_long_audio(mp3_path, output_folder, date_str): """ 处理长音频文件,分割、识别并分析 参数: mp3_path (str): MP3文件路径 prompt (str): 分析提示词 output_folder (str): 输出文件夹路径 返回值: str: 分析结果 """ logger.info("开始处理长音频...") # 转换MP3为WAV格式 logger.info("步骤1/4: 转换MP3为WAV格式") wav_path = convert_mp3_to_wav( mp3_path, os.path.join(output_folder, "input.wav") ) # 分割音频 # 可以选择固定分片或智能静音分割 # chunks = split_audio_by_fixed_duration(wav_path, 180000, output_folder) logger.info("步骤2/4: 智能静音分割音频") chunks = split_audio_by_smart_silence( wav_path, 700, -40, output_folder ) # 存储所有识别文本 transcribed_text = "" # 识别每个分片 logger.info(f"步骤3/4: 开始识别音频分片,共{len(chunks)}个分片") for i, chunk_path in enumerate(chunks): try: logger.info(f"识别进度: {i+1}/{len(chunks)} ({((i+1)/len(chunks)*100):.1f}%)") # 调用音频识别API text = transcribe_audio(chunk_path) """ if not text[1].startswith('今天的新闻联播节目播送完毕'): prompt='请分析所给文本的新闻内容,返回一个简短标题' text.append(analyze_text(text[1],prompt)) else: text.append('') """ # 新闻标题留空 text.append('') # 添加到总文本 #transcribed_text += text + "\n" # 删除临时文件 os.remove(chunk_path) # 初始化数据库连接 db = MySQLDB() # 使用默认参数连接数据库 try: # 插入数据示例 user_data = { "news_days": date_str, "daily_sub_id": i, "news_raw": text[0], "news_improve": text[1], "news_title": text[2] } user_id = db.insert_data("xwlb_daily", user_data) finally: # 关闭连接 db.close() except Exception as e: logger.error(f"识别失败: {chunk_path}, 错误: {e}") # 可以在这里添加重试逻辑 # 分析识别文本 """ print("步骤4/4: 分析识别文本") print(f"识别文本长度: {len(transcribed_text)}") print(f"识别文本内容: {transcribed_text}") if len(transcribed_text) < 1: print("识别文本为空,跳过分析处理") return "识别文本为空,无法进行分析" analysis_result = analyze_text(transcribed_text, prompt) """ logger.info("✓ 长音频处理完成") # 返回分析结果 return "" # 使用示例 if __name__ == "__main__": # MP3文件路径 mp3_path = "20251002.mp3" # 分析提示词 prompt = "请总结这段由中国中央电视台新闻联播音频转为文字的文本,理解其主要内容并提取其中的关键信息。" # 输出文件夹 output_folder = "audio_processing" # 处理长音频 try: result = process_long_audio( mp3_path, prompt, output_folder ) # 打印分析结果 print("分析结果:\n") print(result) except Exception as e: print(f"处理失败: {e}")