
Поставленная задача:
распознавать речь по аудиозаписям диалогов сотрудников и клиентов.
Применяем Whisper.
Работаем в Colab.
Помимо распознавания речи данная модель Whisper имеет штатную функцию тайминга.
Исходный ресурс и документация здесь.
По документации:
Whisper — это универсальная модель распознавания речи. Она обучена на большом наборе данных разнообразного аудио, а также представляет собой многозадачную модель, которая может выполнять многоязычное распознавание речи, перевод речи и идентификацию языка.
Базовое применение по документации
Модель устанавливается стандартно.
Есть несколько модификаций (tiny, base, small, medium, large), в Colab помещаются все.
!pip install -U openai-whisper import whisper model_name = 'large' model = whisper.load_model(model_name)
Базовое применение модели также простое и стандартное.
result = model.transcribe(file) print(result["text"])
Мы же будем распознавать речь двух участников в двухканальном аудиофайле и добавим тайминг.
Загружаем аудиофайл
Загружаем файл с компьютера, запоминаем название, создаем папку для будущего архива.
import os from google.colab import files uploaded = files.upload() file = next(iter(uploaded.keys())) source_file_name = file.replace('.wav','') path = "/content/" + source_file_name os.mkdir(path)
Убеждаемся, что верно сохранили наименование файла и что в файле 2 канала.
print(source_file_name) audio_file = wave.open(source_file_name + '.wav') CHANNELS = audio_file.getnchannels() print("Количество каналов:", CHANNELS)
Определяем язык
# load audio and pad/trim it to fit 30 seconds audio = whisper.load_audio(source_file_name + '.wav') audio = whisper.pad_or_trim(audio) # make log-Mel spectrogram and move to the same device as the model mel = whisper.log_mel_spectrogram(audio).to(model.device) # detect the spoken language _, probs = model.detect_language(mel) print(f"Detected language: {max(probs, key=probs.get)}")
Необходимо обратить внимание, что определение языка осуществляется по первым 30 секундам. Это иногда вводит в заблуждение, так как собеседники могут поприветствовать друг друга на одном языке, а потом определить, что им удобнее разговаривать на другом языке и перейти на другой язык.
Создаем 2 файла по одной дорожке
Эта часть не относится непосредственно к Whisper и к распознаванию речи. Здесь читаем из файла все семплы, обнуляем каждый четный и создаем новый файл. Аналогично поступаем с нечетными семплами.
Код разбивки файла на 2 дорожки
import wave, struct # файл делится на две дорожки и создается два файла audio_file = wave.open(source_file_name + '.wav') SAMPLE_WIDTH = audio_file.getsampwidth() # глубина звука CHANNELS = audio_file.getnchannels() # количество каналов FRAMERATE = audio_file.getframerate() # частота дискретизации N_SAMPLES = audio_file.getnframes() # кол-во семплов на каждый канал N_FRAMES = audio_file.getnframes() # Определяем параметры аудиофайла nchannels = CHANNELS sampwidth = SAMPLE_WIDTH framerate = FRAMERATE nframes = N_FRAMES comptype = "NONE" # тип компрессии compname = "not compressed" # название компрессии # узнаем кол-во семплов и каналов в источнике N_SAMPLES = nframes CHANNELS = nchannels def create_file_one_channel(name): # создаем пустой файл в который мы будем записывать результат обработки в режиме wb (write binary) out_file = wave.open(name, "wb") # в "настройки" файла с результатом записываем те же параметры, что и у "исходника" out_file.setframerate(framerate) out_file.setsampwidth(sampwidth) out_file.setnchannels(CHANNELS) # обратно перегоняем список чисел в байт-строку audio_data = struct.pack(f"<{N_SAMPLES * CHANNELS}h", *values_copy) # записываем обработанные данные в файл с резхультатом out_file.writeframes(audio_data) ########## print('started') # читаем из файла все семплы samples = audio_file.readframes(N_FRAMES) # просим struct превратить строку из байт в список чисел # < - обозначение порядка битов в байте (можно пока всегда писать так) # По середине указывается общее количество чисел, это произведения кол-ва семплов в одном канале на кол-во каналоов # h - обозначение того, что одно число занимает два байта values = list(struct.unpack("<" + str(N_FRAMES * CHANNELS) + "h", samples)) print(values[:20]) values_copy = values[:] # обнулим каждое четное значение for index, i in enumerate(values_copy): if index % 2 == 0: values_copy[index] = 0 create_file_one_channel('1_channel.wav') print(values_copy[:20]) values_copy = values[:] # обнулим каждое нечетное значение for index, i in enumerate(values_copy): if index % 2 != 0: values_copy[index] = 0 create_file_one_channel('2_channel.wav') print(values_copy[:20])
Получили файл 1-го канала и файл 2-го канала: 1_channel.wav и 2_channel.wav.
Транскрибация 1 канала
Транскрибируем файл с 1 каналом
from datetime import timedelta source_file_name_channel = '1_channel' print(source_file_name_channel) # load audio and pad/trim it to fit 30 seconds audio = whisper.load_audio(source_file_name_channel + '.wav') audio = whisper.pad_or_trim(audio) # make log-Mel spectrogram and move to the same device as the model mel = whisper.log_mel_spectrogram(audio).to(model.device) # detect the spoken language _, probs = model.detect_language(mel) print(f"Detected language: {max(probs, key=probs.get)}") print('started...') print() result = model.transcribe(source_file_name_channel + '.wav',) segments = result['segments'] text_massive = [] for segment in segments: startTime = str(0)+str(timedelta(seconds=int(segment['start']))) endTime = str(0)+str(timedelta(seconds=int(segment['end']))) text = segment['text'] segmentId = segment['id']+1 segment = f"{segmentId}. {startTime} - {endTime}\n{text[1:] if text[0] == ' ' else text}" #print(segment) text_massive.append(segment) print() print('Finished')
и сохраняем результат в файл формата docx.
!pip install python-docx from docx import Document # сохраняем текст с таймингом text = text_massive # создаем новый документ doc = Document() # добавляем параграф с текстом doc.add_paragraph(source_file_name + '_' + source_file_name_channel + '_' + model_name) for key in text: doc.add_paragraph(key) # сохраняем документ doc.save(path + '/' + source_file_name + '_' + source_file_name_channel + '_text_timing_' + model_name + '.docx')
Со вторым каналом все то же самое, только вместо "1_channel" применяем "2_channel".
Также возможно применить цикл из этих двух значений, не принципиально.
На данном этапе получили 2 текстовых файла с текстом и таймингом, и эти файла лежат в созданной ранее папке с соответствующим названием. Осталось создать архив и скачать.
Создание и скачивание архива
import shutil/ # Создание архива с файлами из папки shutil.make_archive(path + '_' + model_name, 'zip', path) # Скачивание архива files.download(path + '_' + model_name + '.zip')
Удаление файлов и папок
Если нужно обработать "вручную" еще файл, то следующий код удалит из Colab применяемые и полученные ранее файлы и папки.
# задаем сразу все и сразу все удаляем file_massive = [ source_file_name + '.wav', source_file_name + '_' + model_name + '.zip', '1_channel.wav', '2_channel.wav', path + '/' + source_file_name + '_1_channel_text_timing_' + model_name + '.docx', path + '/' + source_file_name + '_2_channel_text_timing_' + model_name + '.docx' ] # Удаление файла for key in file_massive: print(key) os.remove(key) print() # Удаление папки print(path) os.rmdir(path) print() print('Finished')
Colab очищен и можно работать с другим файлом.
Дополнение
Если файлов несколько и хочется автоматизировать, то возможно закачать все аудиофайлы и запустить все фрагменты подряд в едином цикле.
Совсем для автоматизации возможно скачивать файлы из карточки клиента или из специального хранилища и после транскрибации сохранять диалог обратно в карточку клиента или в специальное хранилище.
Транскрибация сразу на английский
В Whisper есть интересная возможность - переводить сразу на английский, минуя текстовый вывод. Это может быть полезно, если файлы на разных языках и их нужно анализировать единым способом. В этом случае целесообразно приводить все диалоги к английскому и в дальнейшем обрабатывать уже на английском.
Для транскрибации сразу на английский применяется дополнительное указание.
result = model.transcribe(source_file_name_channel + '.wav', task="translate")
Примечание
Если Вы обнару��или в статье неточность, или считаете полезным что-либо добавить - пожалуйста, сообщите в комментариях.

