Один простой трюк, который работает для всех языков

В последнее время функция преобразования речи в текст набирает обороты, поскольку предлагает пользователям совершенно новый интерфейс. Он широко применяется компаниями на рынке, особенно в сфере обслуживания клиентов. Фактически, крупные игроки, такие как 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.

Спасибо, что прочитали эту статью. Не стесняйтесь проверять мои другие статьи. Удачного дня!

использованная литература

  1. Google Speech-to-Text - Получение меток времени слов
  2. Документация Azure - краткое руководство по преобразованию речи в текст