File size: 4,936 Bytes
ea0886a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import torch
import torchaudio
from speechbrain.inference.speaker import SpeakerRecognition
import gradio as gr
from pydub import AudioSegment
import os
import tempfile
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import io
from PIL import Image 

class SpeakerVerification:
    def __init__(self):
        self.verification = SpeakerRecognition.from_hparams(
            source="speechbrain/spkrec-ecapa-voxceleb",
            savedir="pretrained_models/spkrec-ecapa-voxceleb"
        )
        self.threshold = 0.25

    def convert_audio(self, audio_path: str) -> str:
        try:
            file_ext = os.path.splitext(audio_path)[1].lower()
            if file_ext == '.wav':
                return audio_path
            
            audio = AudioSegment.from_file(audio_path)
            with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_wav:
                temp_wav_path = temp_wav.name
                audio.export(temp_wav_path, format='wav')
            return temp_wav_path

        except Exception as e:
            print(f"Error converting audio: {str(e)}")
            raise

    def score_to_probability(self, score: float) -> float:
        scale = 10
        centered_score = (score - self.threshold) * scale
        probability = 1 / (1 + np.exp(-centered_score))
        probability = max(0.0, min(1.0, probability))
        return probability
    
    def verify_speaker(self, audio_path1: str, audio_path2: str) -> tuple[float, str]:
        try:
            wav_path1 = self.convert_audio(audio_path1)
            wav_path2 = self.convert_audio(audio_path2)
            
            score, prediction = self.verification.verify_files(wav_path1, wav_path2)
            
            if wav_path1 != audio_path1:
                os.unlink(wav_path1)
            if wav_path2 != audio_path2:
                os.unlink(wav_path2)
            
            score_value = score.item()
            probability = self.score_to_probability(score_value)
            decision = "Same speaker" if prediction.item() else "Different speakers"
            
            return probability, decision, score_value

        except Exception as e:
            print(f"Error in speaker verification: {str(e)}")
            return 0.0, f"Error: {str(e)}"

    def get_embeddings(self, audio_path: str):
        wav_path = self.convert_audio(audio_path)
        signal, fs = torchaudio.load(wav_path)
        
        if signal.shape[0] > 1:
            signal = torch.mean(signal, dim=0, keepdim=True)
            
        embeddings = self.verification.encode_batch(signal)
        
        if wav_path != audio_path:
            os.unlink(wav_path)
        return embeddings.squeeze()

    def plot_embeddings_comparison(self, emb1, emb2):
        fig = Figure(figsize=(10, 4))
        ax = fig.add_subplot(111)
        
        emb1_np = emb1.cpu().numpy()
        emb2_np = emb2.cpu().numpy()
        
        x = range(len(emb1_np))
        ax.plot(x, emb1_np, label='Speaker 1', alpha=0.7)
        ax.plot(x, emb2_np, label='Speaker 2', alpha=0.7)
        
        ax.set_title('Speaker Embeddings Comparison')
        ax.set_xlabel('Embedding Dimension')
        ax.set_ylabel('Value')
        ax.legend()
        
        buf = io.BytesIO()
        fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
        buf.seek(0)
        
        image = Image.open(buf)
        plt.close(fig)
        return image

def create_gradio_interface():
    speaker_verifier = SpeakerVerification()
    
    def process_audio(audio1, audio2):
        try:
            if audio1 is None or audio2 is None:
                return "Error: Please provide both audio samples", None
            
            probability, decision, score = speaker_verifier.verify_speaker(audio1, audio2)
            emb1 = speaker_verifier.get_embeddings(audio1)
            emb2 = speaker_verifier.get_embeddings(audio2)
            
            embeddings_plot = speaker_verifier.plot_embeddings_comparison(emb1, emb2)
            
            result_text = f"Probability: {probability:.2%}\nCosine distance: {score}\nDecision: {decision}"
            
            return result_text, embeddings_plot
        
        except Exception as e:
            return f"Error processing audio: {str(e)}", None

    interface = gr.Interface(
        fn=process_audio,
        inputs=[
            gr.Audio(label="Audio Sample 1", type="filepath"),
            gr.Audio(label="Audio Sample 2", type="filepath")
        ],
        outputs=[
            gr.Textbox(label="Result"),
            gr.Image(label="Embeddings Comparison", type="pil"),
        ],
        title="Speaker Verification System",
        description="Upload two audio samples to check if they're from the same speaker."
    )
    
    return interface

app = create_gradio_interface()

if __name__ == "__main__":
    app.launch()