File size: 9,243 Bytes
d5a7304
 
 
 
 
40e6055
d5a7304
 
 
dab09e6
d5a7304
 
 
 
bf4454f
 
d5a7304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc54321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5c9298
bc54321
a5c9298
bc54321
a5c9298
bc54321
 
 
 
d5a7304
 
0e2929c
d5a7304
 
 
 
 
 
 
 
 
a694d9e
 
 
d5a7304
 
 
 
a694d9e
d5a7304
a694d9e
 
d5a7304
 
e94a807
 
d5a7304
910aade
d5a7304
 
 
 
 
 
 
945f327
 
 
 
d5a7304
 
 
 
 
 
 
bc54321
d5a7304
 
 
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import gradio as gr 
from transformers import pipeline, Wav2Vec2ProcessorWithLM, Wav2Vec2ForCTC
import os
import soundfile as sf
import torch
import numpy as np

HF_TOKEN = os.environ.get("HF_TOKEN")

model_name = "bond005/wav2vec2-large-ru-golos-with-lm"
processor = Wav2Vec2ProcessorWithLM.from_pretrained(model_name)
model = Wav2Vec2ForCTC.from_pretrained(model_name)
pipe = pipeline("automatic-speech-recognition", model=model, tokenizer=processor, feature_extractor=processor.feature_extractor, decoder=processor.decoder) 

detokenize_dict = {value: key for key, value in processor.tokenizer.get_vocab().items()}

dict_v = ["а", "у" "о" "и" "э" "ы" "я" "ю" "е" "ё"]

def count_char_borders(predicted_ids, input_values, processor, sample_rate=16000):
    predicted_ids_l = predicted_ids[0].tolist()
    duration_sec = input_values.shape[1] / sample_rate

    ids_c_time = [(i / len(predicted_ids_l) * duration_sec, _id) for i, _id in enumerate(predicted_ids_l)]

    t_chars_list = [[i[0], detokenize_dict[i[1]]] for i in ids_c_time if i[1] != processor.tokenizer.pad_token_id]

    t_chars_list_cl = []
    cur = None
    for i, item in enumerate(t_chars_list[:-1]):
      if i == 0 or cur == None:
        cur = item
      if item[1] != t_chars_list[i + 1][1]:
        cur.append(t_chars_list[i + 1][0])
        t_chars_list_cl.append(cur)
        cur = t_chars_list[i + 1]

    t_chars_list_cl = [i if i[1] != "|" else [i[0], "", i[2]] for i in t_chars_list_cl]
    chars, char_start_times, char_end_times = [], [], []
    for c in t_chars_list_cl:
      if c[1].lower() in dict_v and c[1] != "":
          chars.append("v")
      elif c[1] != "":
          chars.append("c")
      else:
          chars.append("")
      char_start_times.append(c[0])
      char_end_times.append(c[2])
    return chars, char_start_times, char_end_times



# обработка seg-файла, получение информации для расчётов
# предполагается, что на вход получаем seg либо 'corpres' - с разметкой по корпресу, либо упрощённая разметка 'cv' - с разметкой на согласные и гласные

def preprocess(chars, starts, labelled='cv'):
    start_and_sound = []
  # берём из seg-файла метки звуков, отсчёты переводим в секунды, получаем общую длительность
    for i, item in enumerate(chars):
        start_time = float(starts[i])
        label = item
        start_and_sound.append([start_time, label])

    # заводим переменные, необходимые для расчётов
    clusters_and_duration = []
    pauses = 0
    sum_dur_vowels = 0
    # флаг для определения границ кластеров. важно, если до и после паузы звуки одного класса
    postpause_flag = 0

    # обработка файлов с гласно-согласной разметкой
    if labelled == 'cv':
      total_duration = 0
      # определяем к какому классу относится каждый звук и считаем длительность (отдельных гласных и согласных кластеров)
      for n, i in enumerate(start_and_sound):
        sound = i[1]
        # определяем не является ли звук конечным
        if n != len(start_and_sound) - 1:
          duration = start_and_sound[n+1][0] - i[0]
          # выделяем гласные
          if sound == 'V' or sound == 'v':
            total_duration += duration
            # записываем отдельно звук в нулевой позиции в обход ошибки индекса
            if n == 0:
              clusters_and_duration.append(['V', duration])

            # объединяем длительности, если предыдущий звук тоже был гласным
            elif clusters_and_duration[-1][0] == 'V' and postpause_flag == 0:
              clusters_and_duration[-1][1] += duration

            # фиксируем длительность отдельного гласного звука
            else:
              clusters_and_duration.append(['V', duration])

            # считаем длителность всех гласных интервалов в записи
            sum_dur_vowels += duration
            # снимаем флаг
            postpause_flag = 0

          # выделяем паузы
          elif sound == '':
            pauses += duration
            total_duration += duration
            # ставим флаг для следующего звука
            postpause_flag = 1

          # выделяем согласные
          else:
            total_duration += duration
            # записываем отдельно звук в нулевой позиции в обход ошибки
            if n == 0:
              clusters_and_duration.append(['C', duration])

            # объединяем длительности, если предыдущий звук тоже был согласным
            elif clusters_and_duration[-1][0] == 'C' and postpause_flag == 0:
              clusters_and_duration[-1][1] += duration

            # фиксируем длительность отдельного согласного звука
            else:
              clusters_and_duration.append(['C', duration])

            # снимаем флаг
            postpause_flag = 0

  # функция возвращает метки кластеров и их длительность и общую длительность всех гласных интервалов
    return clusters_and_duration, sum_dur_vowels, total_duration, pauses


def delta_C(cons_clusters):
  # применяем функцию numpy среднеквадратического отклонения
  dC = np.std(cons_clusters)
  return dC

def percent_V(vowels, total_wo_pauses):
  pV = vowels / total_wo_pauses
  return pV


# point_1 = np.array((0, 0, 0))
# point_2 = np.array((3, 3, 3))
def count_eucl(point_1, point_2):
    # Initializing the points
    # Get the square of the difference of the 2 vectors
    square = np.square(point_1 - point_2)
    # Get the sum of the square
    sum_square = np.sum(square)
    # The last step is to get the square root and print the Euclidean distance
    distance = np.sqrt(sum_square)
    return distance

 
ex_dict = {"eng": np.array((0.0535, 0.401)), "kat": np.array((0.0452, 0.456)), "jap": np.array((0.0356, 0.531))}


def classify_rhytm(dC, pV):
    our = np.array((dC, pV))
    res = {}
    if (dC > 0.08 and pV > 0.45) or (dC < 0.03 and pV < 0.04):
        text = "Вы не укладываетесь ни в какие рамки и прекрасны в этом!"
    else:
        for k, v in ex_dict.items():
            res[k] = count_eucl(our, v)
        
        sorted_tuples = sorted(res.items(), key=lambda item: item[1])
        sorted_res = {k: v for k, v in sorted_tuples}
        if [i for i in sorted_res.keys()][0] == "eng":
            text = "По типу ритма ваша речь близка к тактосчитающим языкам (английский)."
        if [i for i in sorted_res.keys()][0] == "kat":
            text = "По типу ритма ваша речь близка к слогосчитающим языкам (испанский)."
        if [i for i in sorted_res.keys()][0] == "jap":
            text = "По типу ритма ваша речь близка к моросчитающим языкам (японский)."
    return text
        


def transcribe(audio):
    y, sr = sf.read(audio, samplerate=16000)
    input_values = processor(y, sampling_rate=sr, return_tensors="pt").input_values

    logits = model(input_values).logits

    predicted_ids = torch.argmax(logits, dim=-1)
    
    chars, char_start_times, char_end_times = count_char_borders(predicted_ids, input_values, processor)

    clusters_and_duration, sum_dur_vowels, total_duration, pauses = preprocess(chars, char_start_times)
    
    cons_clusters = []
    
    # параметры для ΔC
    for x in clusters_and_duration:
      if x[0] == 'C':
        cons_clusters.append(x[1])
    
    # параметры для %V
    vowels_duration = sum_dur_vowels
    duration_without_pauses = total_duration - pauses

    # расчёт метрик
    dC = delta_C(cons_clusters) / 5
    pV = percent_V(vowels_duration, duration_without_pauses) * 5
    
    transcription = processor.batch_decode(logits.detach().numpy()).text[0]
    
    text = {"transcription": transcription}

    text['dC'] = dC

    text['pV'] = pV
    
    cl = classify_rhytm(dC, pV)
    
    text['result'] = cl
    
    return text

iface = gr.Interface(
    fn=transcribe, 
    inputs=gr.Audio(type="filepath"), 
    outputs="text",
    title="Mihaj/Wav2Vec2RhytmAnalyzer",
    description="Демо анализатор ритма на основе модели Wav2Vec large от bond005.",
)

iface.launch()