Один простой трюк, который работает для всех языков
В последнее время функция преобразования речи в текст набирает обороты, поскольку предлагает пользователям совершенно новый интерфейс. Он широко применяется компаниями на рынке, особенно в сфере обслуживания клиентов. Фактически, крупные игроки, такие как Google и Microsoft, предоставляют свой собственный API преобразования речи в текст как часть своих технологий.
К вашему сведению, большинство расширенных API преобразования речи в текст имеют временные метки на уровне слов.
API преобразования речи в текст Google
Например, при запуске Google's Speech-to-Text API вы получите следующий результат:
[ { "startTime": "1.400s", "endTime": "1.800s", "word": "okay" }, { "startTime": "1.800s", "endTime": "2.300s", "word": "so" }, ... ]
API преобразования речи в текст Azure
С другой стороны, у Microsoft есть собственная служба преобразования речи в текст Azure, которая возвращает следующий результат:
[
{
"Duration": 5500000,
"Offset": 7800000,
"Word": "seven"
},
{
"Duration": 2700000,
"Offset": 13400000,
"Word": "three"
},
...
]
Оба API выглядят многообещающими на первый взгляд, но есть большая проблема, если вы собираетесь использовать вывод для создания субтитров, то есть объединить их в предложения, которые не будут слишком короткими или слишком длинными. Если конечный текст на английском языке, вы все равно можете постобработать и разделить его на основе определенных знаков препинания, таких как:
[',', '.', '!', '?']
В случае с Azure это невозможно, потому что текст во временной метке на уровне слова основан на лексическом (без заглавных букв и знаков препинания). Кроме того, отображаемый текст после транскрипции находится в режиме диктовки, который отличается от текста меткой времени на уровне слова. Например, отображаемый текст:
007 James Bond
будут иметь следующие отметки времени на уровне слова:
[
{
"Duration": 2700000,
"Offset": 35600000,
"Word": "double"
},
{
"Duration": 700000,
"Offset": 38400000,
"Word": "oh"
},
{
"Duration": 4900000,
"Offset": 39200000,
"Word": "seven"
},
{
"Duration": 3900000,
"Offset": 44400000,
"Word": "james"
},
{
"Duration": 3300000,
"Offset": 48400000,
"Word": "bond"
}
]
В результате у вас нет возможности сопоставить результат на основе массива, поскольку длина и текст в какой-то момент различаются. На момент написания статьи разработчик пояснил, что в метках времени на уровне слов нет поддержки режима диктовки и пунктуации.
Это будет намного сложнее, если вы намереваетесь поддерживать несколько языков. Это главным образом потому, что каждый язык имеет свои собственные символы пунктуации, а некоторые языки не токенизуются пробелами.
Разделяйте слова на предложения с помощью отметки времени
После нескольких раундов экспериментов я нашел простой трюк, позволяющий разбивать выходные слова на предложения. Этот метод должен хорошо работать для большинства языков, поскольку он не зависит от выходного текста. Суть в том, чтобы просмотреть весь список и вычислить длину пустых промежутков между ними. Если длина превышает заданный порог, рассматривайте это как разрыв предложения.
Взгляните на следующее изображение в качестве справки:
В этом случае вы получите два предложения:
- сообщается, что
- пять человек получили ранения
Помимо независимости от языка, вы можете точно настроить порог в соответствии со своими предпочтениями, чтобы получить наилучшую сегментацию.
Перейдем к следующему разделу и начнем писать код.
Выполнение
В этом руководстве в качестве справочной информации при выполнении постобработки будут использоваться результаты преобразования речи в текст в Azure. Также для простоты код будет представлен на Python. Сказав это, вы можете легко преобразовать его на другие языки программирования, поскольку внешний пакет не используется.
Инициализация
Создайте новый файл Python в своем рабочем каталоге и инициализируйте его следующими переменными:
Преобразовать наносекунды в секунды
К настоящему времени вы должны были заметить, что и Offset
, и Duration
находятся в наносекундах. Давайте создадим функцию, которая преобразует введенные наносекунды в секунды с двумя десятичными знаками:
def get_seconds(nanoseconds): return round(nanoseconds / 10000000, 2)
Соединяйте слова в предложение
Нам понадобится функция для объединения слов в предложение. Для поддержки нескольких языков вы можете установить условный оператор для определения языка ввода и соединения слов с помощью соответствующего разделителя. Например:
def join_words(words, lang): if lang == 'ja-JP' or lang == 'th-TH': return ''.join(words) return ' '.join(words)
В этом случае тайский и японский языки построены без пробела, а остальные языки будут разделены пробелом. Измените его соответствующим образом в соответствии с вашими предпочтениями.
Проверить различия
Следующим шагом является реализация функции постобработки. Перед этим давайте пройдемся по списку в зависимости от его длины и вычислим разницу в длине пробела между каждым словом.
Цикл начнется с 0 и закончится при общей длине списка минус 1. Это главным образом потому, что разница вычисляется следующим образом:
Offset of next word - (Offset + Duration of current word)
Вы должны увидеть на консоли следующий вывод (без маркеров стрелок):
0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.25 <-- 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.41 <-- 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01
Строить предложения
Это два случая, когда продолжительность превышает заданный порог 0,1. Закомментируйте оператор print
и измените цикл следующим образом:
Код работает следующим образом:
- рассчитать разницу
- установить
start
, если токены пусты, что указывает на начало предложения - построить предложение, если разница превышает пороговое значение, добавить информацию в виде словаря внутри переменной
timestamp
, очиститьtokens
и установитьstart
на следующийOffset
- если дошло до второго последнего слова, добавить последнее слово и выйти из цикла
Обратите внимание, что в приведенной выше реализации в качестве времени end
используется Offset + Duration
. Измените его соответствующим образом в соответствии с вашими предпочтениями.
Могут быть случаи, когда он возвращает только один элемент в списке. В этой ситуации цикл завершится и вернет пустой список. Вы можете легко справиться с этим, добавив оператор условной проверки следующим образом:
if len(data) == 1: timestamp.append({'segment': data[0]['Word'], 'start': get_seconds(data[0]['Offset']), 'end': get_seconds(data[0]['Offset'] + data[0]['Duration'])}) else: # the rest of the code (loop) ...
Вы можете найти полный код по следующей сути:
Тест и вывод
Запустите его следующим образом:
python script.py
Вы должны получить следующий результат:
[{'segment': 'average household income is up ten percent from four years ago', 'start': 5.11, 'end': 8.52}, {'segment': 'and our customers are spending twenty percent more per transaction', 'start': 8.77, 'end': 12.12}, {'segment': 'nearly everyone surveyed is employed in a professional or managerial occupation', 'start': 12.53, 'end': 16.8}]
Вы можете использовать список вывода для создания собственного файла субтитров, будь то формат SRT или VTT. Например, преобразованный файл SRT должен иметь следующий вид:
1 00:00:05,110 --> 00:00:08,520 average household income is up ten percent from four years ago 2 00:00:08,770 --> 00:00:12,120 and our customers are spending twenty percent more per transaction 3 00:00:12,530 --> 00:00:16,800 nearly everyone surveyed is employed in a professional or managerial occupation
Что касается VTT, вывод должен быть следующим:
WEBVTT 00:00:05.110 --> 00:00:08.520 average household income is up ten percent from four years ago 00:00:08.770 --> 00:00:12.120 and our customers are spending twenty percent more per transaction 00:00:12.530 --> 00:00:16.800 nearly everyone surveyed is employed in a professional or managerial occupation
Вывод
Подведем итоги тому, что вы узнали сегодня.
Эта статья началась с краткого объяснения преобразования речи в текст и проблемы, с которой столкнулись разработчики при использовании выходных меток времени на уровне слов из API преобразования речи в текст.
Затем было предложено рабочее решение, не зависящее от языка, которое заключается в разделении на основе пустых промежутков между каждым словом.
Он продолжил и объяснил весь процесс реализации, который использует API преобразования речи в текст Azure в качестве справочного материала. В конце вы должны получить список словарей, которые можно использовать для создания соответствующего файла субтитров в формате SRT или VTT.
Спасибо, что прочитали эту статью. Не стесняйтесь проверять мои другие статьи. Удачного дня!