Поставленная задача:
распознавать речь по аудиозаписям диалогов сотрудников и клиентов.
Применяем 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")
Примечание
Если Вы обнаружили в статье неточность, или считаете полезным что-либо добавить - пожалуйста, сообщите в комментариях.