Skip to content

Как размечать расшифровки Whisper

Как взять вывод Whisper или WhisperX и превратить его в работающий проект разметки: какой файл сохранять, когда нужна диаризация, как назначать говорящих и как выгрузить результат, не потеряв тайминги.

Whisper выдаёт сегменты с таймингами, и инструмент разметки должен читать их как есть. Сохраняйте вывод .json, а не .txt, запустите WhisperX, если нужны метки говорящих, и направьте Potato на папку. Ни скриптов конвертации, ни этапа переформатирования. Разбор по каждому формату — в форматах расшифровок.

Whisper — открытая модель распознавания речи от OpenAI, ставшая первым шагом по умолчанию для всех, у кого накопилась гора аудио. На выходе получается набор сегментов с таймингами, а это почти всё, что нужно проекту разметки. Пробел — всё, что лежит между «расшифровки есть» и «разметчики их размечают».

Замечание: Potato не расшифровывает. Распознавание речи идёт до него, а Potato принимает результат. Это руководство разбирает, какие решения на предыдущем этапе важны, но сама расшифровка происходит до того, как Potato что-либо увидит.

Какой файл вывода Whisper сохранять?

Whisper пишет несколько файлов, и выбор здесь важнее, чем кажется:

bash
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True
ФайлЧто содержитБрать?
.jsonСегменты с временем начала и конца, при желании с пословными таймингамиДа, именно этот
.srt / .vttСегменты с таймингами, без метаданныхДа, работает нормально
.tsvНачало и конец в миллисекундах плюс текстДа
.txtТолько текст, без тайминговНет, синхронизировать со звуком нечем

Если у вас остался только .txt, выравнивание потеряно и восстановить его без повторного прогона модели нельзя. Это самая частая причина, по которой расшифровка попадает в Potato одним нерасчленённым куском текста.

Размечает ли Whisper говорящих?

Нет. Обычный Whisper расшифровывает, но не сообщает, кто говорит. Каждая реплика приходит без привязки — для разметки содержания это нормально и мучительно для всего, что связано с говорящими.

Диаризация говорящих — отдельный шаг. Три способа с ней справиться:

Запустите WhisperX, который оборачивает Whisper диаризацией через pyannote:

bash
whisperx interview_01.mp3 --model medium --diarize --output_format json

В его выводе у каждого сегмента есть поле speaker (SPEAKER_00, SPEAKER_01 и так далее), и Potato читает его напрямую.

Возьмите облачный API с включённой диаризацией. Deepgram с diarize=true, AssemblyAI со speaker_labels, AWS Transcribe и Rev.ai — все выдают метки говорящих, и Potato читает все четыре формата изначально.

Three transcript turns with hatched grey backgrounds, each labeled Unassigned with a dropdown caret.Undiarized turns render as Unassigned with a speaker picker

Отдайте это разметчикам. Когда реплики приходят без диаризации, каждый пузырь рисуется как Unassigned с выпадающим списком. На грязном звуке, при накладывающейся речи или в комнате с фоновым шумом слушающий человек часто точнее автоматической диаризации. Для небольшого корпуса это разумный обмен, а не запасной вариант.

Как собрать файл данных?

Направьте конвертер на папку с выводом Whisper:

bash
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json

Расшифровки сопоставляются с медиа по базовому имени, так что interview_01.json найдёт interview_01.mp3. Идентификаторы объектов берутся из имени файла, а удвоенное расширение Whisper interview_01.mp3.json обрабатывается так, что идентификатор interview_01.mp3 не появляется.

Проверьте, что он понял, прежде чем на это опираться:

bash
potato transcripts ./whisper_out --dry-run
text
Scanned 3 file(s):
  interview_01.json      Whisper JSON      42 turns    891.4s  undiarized
  interview_02.json      WhisperX JSON     51 turns   1120.8s  2 speaker(s): SPEAKER_00, SPEAKER_01
  interview_03.json      Whisper JSON      38 turns    754.2s  undiarized

3 item(s), 131 turn(s).

Файл, о котором сообщается plain text или ноль реплик, — это ваш проблемный файл. Почти всегда это .txt, случайно попавший в папку с выводом .json.

Можно ли пропустить конвертацию?

Да. Файл данных может ссылаться прямо на файлы расшифровок на диске:

json
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
                                   "transcript": "media/int_001.srt"}}

Potato прочитает файл, определит формат по содержимому и нормализует его при отрисовке объекта. Ваши расшифровки остаются файлами, которые можно сравнивать и переэкспортировать, вместо того чтобы быть запечёнными в кусок данных.

Как выглядит конфигурация?

Сформированный файл данных имеет такой вид:

json
{
  "id": "interview_01",
  "conversation": {
    "audio": "audio/interview_01.mp3",
    "turns": [
      {"turn_id": "t0", "speaker": "SPEAKER_00", "start": 0.0, "end": 6.5,
       "text": "Welcome back."}
    ]
  }
}

А вот подходящая конфигурация, которую potato transcripts --emit-config напечатает за вас:

yaml
annotation_task_name: "Interview Annotation"
task_dir: .
data_files:
  - data/interviews.json
 
item_properties:
  id_key: id
  text_key: conversation
 
instance_display:
  fields:
    - key: conversation
      type: audio_dialogue
      label: "Transcript"
      span_target: true
      display_options:
        show_timestamps: true
        allow_speaker_assignment: auto
 
annotation_schemes:
  - annotation_type: span
    name: topics
    description: "Highlight topic mentions"
    labels:
      - name: policy
      - name: personal

Transcript turns rendered as colored speaker bubbles with per-turn play buttons, timestamps, and an inline label question under each turn.The audio_dialogue display, with a per-turn question attached to every turn

Отображение audio_dialogue рисует реплики цветными пузырями говорящих, синхронизированными со звуком, и у каждой реплики есть кнопка воспроизведения, играющая только её. span_target: true позволяет спанам пересекать границы реплик, а смещения остаются устойчивыми, когда говорящего переназначают.

Если вам нужнее, чтобы разметчики проверяли саму расшифровку, а не размечали её содержание, speech_transcript даст карточки сегментов с тегами ошибок и полем исправления. Для работы с наложениями и перебиваниями voice_interaction даёт двухдорожечный таймлайн. Оба читают те же файлы.

Как разметчики назначают говорящих?

Задайте список, чтобы говорящие получали устойчивые имена, цвета и стороны:

yaml
display_options:
  allow_speaker_assignment: auto
  speakers:
    - id: interviewer
      name: "Interviewer"
      color: "#7c3aed"
      side: left
    - id: participant
      name: "Participant"
      color: "#059669"
      side: right

auto включает выбор говорящего, когда есть недиаризованные реплики. Поставьте true, чтобы разрешить переназначение даже там, где источник говорящих разметил, — это нужно, если вы исправляете ошибки диаризации, а не заполняете пустоты.

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

Как выгрузить разметку обратно?

Экспорт в JSON, JSONL и CSV работает как обычно. Когда временное выравнивание должно сохраниться, выгружайте многоуровневую разметку в ELAN EAF или Praat TextGrid:

bash
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/

Оба формата ходят в обе стороны. Potato читает EAF и TextGrid и на вход, так что можно размечать в Potato, доводить в ELAN или Praat и читать результат обратно.

Что почитать дальше