Files
myquant/djapi/api/video/audioRead.py
T
2026-06-17 20:48:42 +08:00

439 lines
16 KiB
Python

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-d2d65b726068445b98b88fc3b675dbf1" # 替换为你的API 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}")