Skip to main content

Руководство по синтезу речи

Подготовка

В этом руководстве можно посмотреть, как и для каких целей используются методы синтеза речи с примерами.

Эндпоинт для синтеза — api.tinkoff.ai:443.

  1. Для авторизации в сервисах получите ключи и вставьте их в переменные среды.

    export VOICEKIT_API_KEY="PUT_YOUR_API_KEY_HERE"
    export VOICEKIT_SECRET_KEY="PUT_YOUR_SECRET_KEY_HERE"
  2. Склонируйте репозиторий с примерами.

    git clone --recursive https://github.com/Tinkoff/voicekit-examples.git
  3. Установите зависимости.

    sudo apt-get install python3 python-pyaudio python3-pyaudio
    sudo python3 -m pip install -r python/requirements/all.txt
    cd python/snippets

В сервисе есть 3 метода для синтеза речи:

Потоковый синтез речи

Метод нужен для синтеза речи в реальном времени — с помощью него получать синтезированный аудиопоток для произвольного текста по мере синтеза.

Пример 1. Несжатое аудио

./tts_streaming_synthesize_linear16_to_wav.py
  1. Синтезируем в Linear16 48KHz и сохраняем в WAV.

  2. Импортируем модули.

    from tinkoff.cloud.tts.v1 import tts_pb2_grpc, tts_pb2
    from auth import authorization_metadata
    import grpc
    import os
    import wave
  3. Получаем конфигурацию.

    # можно получать из переменных среды или заменить в сниппете
    endpoint = os.environ.get("VOICEKIT_ENDPOINT") or "api.tinkoff.ai:443"
    api_key = os.environ["VOICEKIT_API_KEY"]
    secret_key = os.environ["VOICEKIT_SECRET_KEY"]

    sample_rate = 48000
  4. Создаем запрос.

    def build_request():
    return tts_pb2.SynthesizeSpeechRequest(
    input=tts_pb2.SynthesisInput(
    text="И мысли тоже тяжелые и медлительные, падают неторопливо и редко "
    "одна за другой, точно песчинки в разленившихся песочных часах."
    ),
    audio_config=tts_pb2.AudioConfig(
    audio_encoding=tts_pb2.LINEAR16,
    sample_rate_hertz=sample_rate,
    ),
    )
  5. Открываем .wav-файл для записи синтезированного аудио.

  6. Шлём запрос и записываем полученные семплы.

    Попутно печатаем приблизительную длительность аудио:

    with wave.open("synthesized.wav", "wb") as f:
    f.setframerate(sample_rate)
    f.setnchannels(1)
    f.setsampwidth(2)

    stub = tts_pb2_grpc.TextToSpeechStub(
    grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()))
    request = build_request()
    metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts")
    responses = stub.StreamingSynthesize(request, metadata=metadata)
    for key, value in responses.initial_metadata():
    if key == "x-audio-duration-seconds":
    print("Estimated audio duration is {:.2f} seconds".format(float(value)))
    break
    for stream_response in responses:
    f.writeframes(stream_response.audio_chunk)
  7. При синтезировании больших объемов текста нужно подобрать размеры сообщения, отвечающие размеру аудио:

    # increase this number if you are trying to synthesize really long text
    ONE_HUNDRED_MEGABYTE_BUFFER = 100 * 1024 * 1024
    grpc.secure_channel(
    endpoint,
    grpc.ssl_channel_credentials(),
    options=[
    ("grpc.max_message_length", ONE_HUNDRED_MEGABYTE_BUFFER),
    ("grpc.max_receive_message_length", ONE_HUNDRED_MEGABYTE_BUFFER),
    ],
    )

Исходный код примера

Пример 2. Потоковое воспроизведение

./tts_streaming_synthesize_linear16_playback.py

Для удобства тестирования синтезируем и воспроизводим на звуковой карте входящий поток.

  1. Импортируем pyaudio:

    import pyaudio
  2. Открываем устройство воспроизведения на звуковой карте.

    pyaudio_lib = pyaudio.PyAudio()
    f = pyaudio_lib.open(output=True, channels=1, format=pyaudio.paInt16, rate=sample_rate)
  3. Пишем семплы в устройство.

    for stream_response in responses:
    f.write(stream_response.audio_chunk)

Исходный код примера

Пример 3. Синтез в A-law (PCMA)

./tts_streaming_synthesize_a_law_playback.py

A-law позволяет сократить сетевые издержки за счет того, что один семпл занимает 1 байт — с компандированием.

В Python есть встроенный модуль для работы с различными PCM кодировками — audioop.

  1. Импортируем модуль.

    import audioop
  2. Для синтеза в A-law указываем кодировку в поле запроса.

    audio_encoding=tts_pb2.ALAW,
  3. Для конвертации семплов из A-law в Linear16 достаточно использовать функцию audioop.alaw2lin с аргументом width=2 — 2 байта на семпл.

    pcm_chunk = audioop.alaw2lin(stream_response.audio_chunk, 2)

Исходный код примера

Пример 4. Синтез в Opus

./tts_streaming_synthesize_raw_opus_playback.py

Синтезируем в Opus и воспроизводим на звуковой карте.

Sample rate для Opus не имеет значения, но примерная длительность аудио передается в семплах исходя из заданного значения sample rate.

  1. Импортируем модуль для декодирования Opus.

    import opuslib
  2. Актуализируем sample rate.

    sample_rate = 16000
  3. Выставляем кодек.

    audio_encoding=tts_pb2.RAW_OPUS,
  4. Инициализируем декодер.

    opus_decoder = opuslib.Decoder(sample_rate, 1)
  5. Пишем декодированные фреймы в устройство.

    f.write(opus_decoder.decode(stream_response.audio_chunk, 5760))

5760 — максимальный размер фрейма: в адекватном биндинге libopus для Python вычислялся бы автоматически в зависимости от частоты дискретизации.

Исходный код примера

Пример 5. Использование SSML

./tts_streaming_synthesize_ssml_linear16_playback.py

Подробнее об использовании SSML-тегов

Для синтеза SSML в SynthesisInput используется поле ssml вместо text, а сам текст оборачивается в тег <speak>:

def build_request():
return tts_pb2.SynthesizeSpeechRequest(
input=tts_pb2.SynthesisInput(
ssml="""
<speak>
<voice name="anna">
<p>
<s>
<emphasis level="strong">Оригинальная</emphasis> мысль?
</s>
<s>
Нет ничего легче.
</s>
</p>
<break time='300ms'/>
<p>
<s>
<voice name="anna">
Библиотеки просто набиты ими.
</voice>
</s>
</p>
</voice>
</speak>
"""
),
audio_config=tts_pb2.AudioConfig(
audio_encoding=tts_pb2.LINEAR16,
sample_rate_hertz=sample_rate,
),
)

Исходный код примера

Пример 6. Выбор голоса

./tts_streaming_synthesize_voice_selection_linear16_playback.py

Чтобы выбрать голос, в SynthesizeSpeechRequest явно задается поле voice:

def build_request():
return tts_pb2.SynthesizeSpeechRequest(
input=tts_pb2.SynthesisInput(
text="Привет! Я Алёна. Я помогу в озвучке книг, новостей, образователь"
"ных курсов, а также могу быть твоим напарником для медитации."
),
audio_config=tts_pb2.AudioConfig(
audio_encoding=tts_pb2.LINEAR16,
sample_rate_hertz=sample_rate,
),
voice=tts_pb2.VoiceSelectionParams(
name="anna"
),
)

Исходный код примера

Пример 7. Получение метаданных

./tts_streaming_synthesize_initial_metadata.py

Синтез отправляет три заголовка в начальных метаданных:

  1. x-request-id — уникальный идентификатор запроса.
  2. x-audio-num-samples — примерное число семплов аудио.
  3. x-audio-duration-seconds — примерная длительность аудио в секундах.
responses = stub.StreamingSynthesize(request, metadata=metadata)
for key, value in responses.initial_metadata():
if key == "x-request-id":
print("RequestId is {}".format(value))
if key == "x-audio-num-samples":
print("Estimated audio duration is {} samples".format(value))
if key == "x-audio-duration-seconds":
print("Estimated audio duration is {:.2f} seconds".format(float(value)))

При обращении в службу поддержки желательно указывать значение x-request-id запроса.

Исходный код примера

Непотоковый синтез речи

Пример 8. Несжатое аудио

./tts_synthesize_linear16_to_wav.py

Для синтеза аудио целиком используется метод Synthesize. Достаточно передать запрос и метаданные в том же формате, что и для метода StreamingSynthesize:

stub = tts_pb2_grpc.TextToSpeechStub(grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()))
request = build_request()
metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts")
response = stub.Synthesize(request, metadata=metadata)

Аудио находится в поле audio_content ответа:

with wave.open("synthesized.wav", "wb") as f:
f.setframerate(sample_rate)
f.setnchannels(1)
f.setsampwidth(2)
f.writeframes(response.audio_content)

Исходный код примера

Пример 9. Воспроизведение несжатого аудио

./tts_synthesize_linear16_playback.py

Для воспроизведения аудио на звуковой карте достаточно передать содержимое поля audio_content в поток воспроизведения:

pyaudio_lib = pyaudio.PyAudio()
f = pyaudio_lib.open(output=True, channels=1, format=pyaudio.paInt16, rate=sample_rate)
f.write(response.audio_content)
f.stop_stream()
f.close()

Исходный код примера

Пример 10. REST API

./tts_synthesize_rest_linear16_playback.py
  1. Импортируем модуль:

    • requests — для отправки HTTP-запросов.

      import requests
    • httpx— для отправки HTTP2-асинхронных запросов.

      import httpx
  2. Создаем запрос одним из способов:

    • Используя google.protobuf.json_format.MessageToDict.

      from tinkoff.cloud.tts.v1 import tts_pb2
      from google.protobuf.json_format import MessageToDict

      def build_request_from_pb():
      pb_request = tts_pb2.SynthesizeSpeechRequest(
      input=tts_pb2.SynthesisInput(
      text="И мысли тоже тяжелые и медлительные, падают неторопливо и редко одна за другой, точно песчинки "
      "в разленившихся песочных часах.",
      ),
      audio_config=tts_pb2.AudioConfig(
      audio_encoding=tts_pb2.LINEAR16,
      sample_rate_hertz=sample_rate,
      ),
      voice=tts_pb2.VoiceSelectionParams(
      name="anna",
      ),
      )
      return MessageToDict(pb_request)
    • Сразу — в формате словаря:

      def build_request():
      return {
      "input": {
      "text": "И мысли тоже тяжелые и медлительные, падают неторопливо и редко одна за другой, точно песчинки "
      "в разленившихся песочных часах.",
      },
      "audioConfig": {
      "audioEncoding": "LINEAR16",
      "sampleRateHertz": sample_rate,
      },
      "voice": {
      "name": "anna",
      }
      }
  3. Для синтеза аудио целиком через REST API достаточно отправить POST-запрос к методу /v1/tts:synthesize с данными в формате JSON.

    Подключите HTTP2 и в метаданных передайте поля авторизации, как в методе StreamingSynthesize.

Сгенерировать аудио размером больше 1 Мб в REST API можно только при использовании протокола HTTP/2 — например, через HTTPX. При использовании HTTP/1 запросы размером большее 1 Мб не исполнятся.

В gRPC API таких ограничений нет.

  • Пример для модуля requests:

    request = build_request()
    metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts", type=dict)
    response = requests.post(f"http{'s' if endpoint.endswith('443') else ''}://{endpoint}/v1/tts:synthesize", json=request, headers=metadata)
  • Пример для модуля httpx:

    async def main():
    async with httpx.AsyncClient(http2=True) as client:
    request = build_request()
    metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts", type=dict)
    response = await client.post(f"http{'s' if endpoint.endswith('443') else ''}://{endpoint}/v1/tts:synthesize", json=request, headers=metadata)

Синтезированное аудио закодировано в Base64-строке в поле audio_content ответа:

if response.status_code != 200:
print(f"REST failed with HTTP code {response.status_code}\nHeaders: {response.headers}\nBody: {response.text}")
else:
response = response.json()
pyaudio_lib = pyaudio.PyAudio()
f = pyaudio_lib.open(output=True, channels=1, format=pyaudio.paInt16, rate=sample_rate)
f.write(base64.b64decode(response["audio_content"]))
f.stop_stream()
f.close()

Исходный код примера

Пример 11. REST API через cURL

./tts_synthesize_rest_linear16_playback.sh
  1. Генерируем авторизационный JWT с помощью shell-скрипта gen_jwt.sh:

    TEN_MINUTES=600
    JWT=$(
    ./gen_jwt.sh --api_key "${VOICEKIT_API_KEY}" \
    --secret_key "${VOICEKIT_SECRET_KEY}" \
    --scope tinkoff.cloud.tts \
    --exp $(("$(date +%s)" + "${TEN_MINUTES}"))
    )

    Фиксируем sample rate.

    SAMPLE_RATE=24000
  2. Создаем JSON-запрос одним из способов:

    • Используя jq:

      request="$(
      jq --null-input \
      --arg input_text 'Мы подсчитали, что шанс «один на миллион» выпадает в девяти случаях из десяти.' \
      --arg audio_encoding 'LINEAR16' \
      --arg sample_rate "${SAMPLE_RATE}" \
      --arg voice_name 'anna' \
      '
      .input.text = $input_text |
      .audioConfig.audioEncoding = $audio_encoding |
      .audioConfig.sampleRateHertz = ($sample_rate | tonumber) |
      .voice.name = $voice_name
      '
      )"
    • Cразу:

      request='{
      "input": {
      "text": "Мы подсчитали, что шанс «один на миллион» выпадает в девяти случаях из десяти."
      },
      "audioConfig": {
      "audioEncoding": "LINEAR16",
      "sampleRateHertz": 48000
      },
      "voice": {
      "name": "anna"
      }
      }'
  3. С помощью cURL отправляем POST-запрос к методу /v1/tts:synthesize, не забывая про токен авторизации в метаданных.

    Синтезированное аудио закодировано в Base64-строке в поле audio_content ответа. Достать его можно при помощи jq.

После этого аудио можно декодировать из Base64 и воспроизвести при помощи команды play из SoX:

curl --header "Content-Type: application/json" \
--header "Authorization: Bearer ${JWT}" \
--request POST \
--data "${request}" \
https://api.tinkoff.ai:443/v1/tts:synthesize | jq --raw-output .audio_content | base64 -d | play --type s16 --rate "${SAMPLE_RATE}" --channels 1 -

Исходный код примера

Список доступных голосов

Пример 12. gRPC API

./tts_list_voices.py

Чтобы получить список доступных голосов, достаточно вызвать метод ListVoices. В метаданных нужно передать данные для авторизации, как в примерах выше:

stub = tts_pb2_grpc.TextToSpeechStub(grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()))
request = tts_pb2.ListVoicesRequest()
metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts")
response = stub.ListVoices(request, metadata=metadata)

Список доступных голосов находится в поле voices ответа:

print("Allowed voices:")
for voice in sorted(response.voices, key=lambda voice: voice.name):
print(f"- {voice.name}")

Исходный код примера

Пример 13. REST API

./tts_list_voices_rest.py
  1. Импортируем модуль:

    • requests — для отправки HTTP-запросов.

      import requests
    • httpx— для отправки HTTP2-асинхронных запросов.

      import httpx
  2. Для получения списка доступных голосов достаточно отправить GET-запрос к методу /v1/tts:list_voices:

    • Пример для модуля requests:

      metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts", type=dict)
      response = requests.get(f"http{'s' if endpoint.endswith('443') else ''}://{endpoint}/v1/tts:list_voices", headers=metadata)
    • Пример для модуля httpx:

      async with httpx.AsyncClient(http2=True) as client:
      metadata = authorization_metadata(api_key, secret_key, "tinkoff.cloud.tts", type=dict)
      response = await client.get(f"http{'s' if endpoint.endswith('443') else ''}://{endpoint}/v1/tts:list_voices", headers=metadata)

В ответе передается JSON со списком голосов:

if response.status_code != 200:
print(f"REST failed with HTTP code {response.status_code}\nHeaders: {response.headers}\nBody: {response.text}")
else:
response = response.json()
print("Allowed voices:")
for voice in sorted(response["voices"], key=lambda voice: voice["name"]):
print(f"- {voice['name']}")

Исходный код примера

Пример 14. REST API через cURL

./tts_list_voices_rest.sh
  1. Генерируем авторизационный JWT с помощью shell-скрипта gen_jwt.sh.

    ```shell
    TEN_MINUTES=600
    JWT=$(
    ./gen_jwt.sh --api_key "${VOICEKIT_API_KEY}" \
    --secret_key "${VOICEKIT_SECRET_KEY}" \
    --scope tinkoff.cloud.tts \
    --exp $(("$(date +%s)" + "${TEN_MINUTES}"))
    )
    ```
  2. С помощью cURL отправляем GET-запрос к методу /v1/tts:list_voices, не забывая про токен авторизации в метаданных.

Ответ можно парсить при помощи jq:

curl --header "Content-Type: application/json" \
--header "Authorization: Bearer ${JWT}" \
--request GET \
https://api.tinkoff.ai:443/v1/tts:list_voices | jq

Исходный код примера

openapi@tbank.ru

АО «ТБанк» использует файлы «cookie» с целью персонализации сервисов и повышения удобства пользования веб-сайтом. «Cookie» представляют собой небольшие файлы, содержащие информацию о предыдущих посещениях веб-сайта. Если вы не хотите использовать файлы «cookie», измените настройки браузера.