speaker / app.py
QLWD's picture
Update app.py
3152b48 verified
raw
history blame
5.89 kB
import torch
import spaces
import gradio as gr
import os
from pyannote.audio import Pipeline
from pydub import AudioSegment
# 获取 Hugging Face 认证令牌
HF_TOKEN = os.environ.get("HUGGINGFACE_READ_TOKEN")
pipeline = None
# 尝试加载 pyannote 模型
try:
pipeline = Pipeline.from_pretrained(
"pyannote/speaker-diarization-3.1", use_auth_token=HF_TOKEN
)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
pipeline.to(device)
except Exception as e:
print(f"Error initializing pipeline: {e}")
pipeline = None
# 音频拼接函数:拼接目标音频和混合音频,返回目标音频的起始时间和结束时间作为字典
def combine_audio_with_time(target_audio, mixed_audio):
if pipeline is None:
return "错误: 模型未初始化"
# 打印文件路径,确保文件正确传递
print(f"目标音频文件路径: {target_audio}")
print(f"混合音频文件路径: {mixed_audio}")
# 加载目标说话人的样本音频
try:
target_audio_segment = AudioSegment.from_wav(target_audio)
except Exception as e:
return f"加载目标音频时出错: {e}"
# 加载混合音频
try:
mixed_audio_segment = AudioSegment.from_wav(mixed_audio)
except Exception as e:
return f"加载混合音频时出错: {e}"
# 记录目标说话人音频的时间点(精确到0.01秒)
target_start_time = len(mixed_audio_segment) / 1000 # 秒为单位,精确到 0.01 秒
# 目标音频的结束时间(拼接后的音频长度)
target_end_time = target_start_time + len(target_audio_segment) / 1000 # 秒为单位
# 将目标说话人的音频片段添加到混合音频的最后
final_audio = mixed_audio_segment + target_audio_segment
final_audio.export("final_output.wav", format="wav")
# 返回目标音频的起始时间和结束时间
return {"start_time": target_start_time, "end_time": target_end_time}
# 使用 pyannote/speaker-diarization 对拼接后的音频进行说话人分离
@spaces.GPU(duration=60 * 2) # 使用 GPU 加速,限制执行时间为 120 秒
def diarize_audio(temp_file):
if pipeline is None:
return "错误: 模型未初始化"
try:
diarization = pipeline(temp_file)
except Exception as e:
return f"处理音频时出错: {e}"
# 返回 diarization 类对象
return diarization
# 将时间戳转换为秒
def timestamp_to_seconds(timestamp):
try:
h, m, s = map(float, timestamp.split(':'))
return 3600 * h + 60 * m + s
except ValueError as e:
print(f"转换时间戳时出错: '{timestamp}'. 错误: {e}")
return None
# 计算时间段的重叠部分(单位:秒)
def calculate_overlap(start1, end1, start2, end2):
overlap_start = max(start1, start2)
overlap_end = min(end1, end2)
overlap_duration = max(0, overlap_end - overlap_start)
return overlap_duration
# 获取目标时间段和说话人时间段的重叠比例
def get_best_match(target_time, diarization_output):
target_start_time = target_time['start_time']
target_end_time = target_time['end_time']
# 通过 diarization_output 获取说话人信息
speaker_segments = []
for speech_turn in diarization_output.itertracks(yield_label=True): # 使用 itertracks 获取每个说话人的信息
start_seconds = speech_turn[0].start
end_seconds = speech_turn[0].end
label = speech_turn[1]
# 计算目标音频时间段和说话人时间段的重叠时间
overlap = calculate_overlap(target_start_time, target_end_time, start_seconds, end_seconds)
overlap_ratio = overlap / (end_seconds - start_seconds)
# 记录说话人标签和重叠比例
speaker_segments.append((label, overlap_ratio, start_seconds, end_seconds))
# 按照重叠比例排序,返回重叠比例最大的一段
best_match = max(speaker_segments, key=lambda x: x[1], default=None)
return best_match
# 处理音频文件并返回输出
def process_audio(target_audio, mixed_audio):
# 打印文件路径,确保传入的文件有效
print(f"处理音频:目标音频: {target_audio}, 混合音频: {mixed_audio}")
# 进行音频拼接并返回目标音频的起始和结束时间(作为字典)
time_dict = combine_audio_with_time(target_audio, mixed_audio)
# 执行说话人分离
diarization_result = diarize_audio("final_output.wav")
if isinstance(diarization_result, str) and diarization_result.startswith("错误"):
return diarization_result, None # 出错时返回错误信息
else:
# 获取最佳匹配的说话人标签和时间段
best_match = get_best_match(time_dict, diarization_result)
if best_match:
# 返回最佳匹配说话人的标签和时间段
return best_match[0], best_match[2], best_match[3]
# Gradio 接口
with gr.Blocks() as demo:
gr.Markdown("""
# 🗣️ 音频拼接与说话人分类 🗣️
上传目标音频和混合音频,拼接并进行说话人分类。结果包括最佳匹配说话人的时间段。
""")
mixed_audio_input = gr.Audio(type="filepath", label="上传混合音频")
target_audio_input = gr.Audio(type="filepath", label="上传目标说话人音频")
process_button = gr.Button("处理音频")
# 输出结果
diarization_output = gr.Textbox(label="最佳匹配说话人")
time_range_output = gr.Textbox(label="最佳匹配时间段")
# 点击按钮时触发处理音频
process_button.click(
fn=process_audio,
inputs=[target_audio_input, mixed_audio_input],
outputs=[diarization_output, time_range_output]
)
demo.launch(share=True)