Бонусы за приглашения

Как работают бонусы за приглашения

Поделитесь ссылкой. Когда друг зарегистрируется по ней и пополнит баланс, вы получите указанный бонус за его последующие пополнения.

Транскрипция встречи в Gemini 3.5 Transcribe: спикеры, таймкоды и словарь на практике

Практическое руководство по превращению 45-минутной записи встречи в выверенный протокол или основу для субтитров с помощью Gemini 3.5 Transcribe: выбор конфигурации, корректное разделение длинного аудио, сборка пословных таймкодов на Python SDK и регламент ручной проверки.

Содержание
Транскрипция встречи в Gemini 3.5 Transcribe: спикеры, таймкоды и словарь на практике

Превращение аудиозаписи рабочей встречи в надежный рабочий документ требует четкого выбора архитектурных параметров распознавания. В Gemini 3.5 Transcribe универсального режима «все включено» не существует: глубокая редакторская очистка (smart), разделение по говорящим (diarization_mode), пословная временная сетка (timestamp_granularities) и узкоспециализированный глоссарий (custom_vocabulary) представляют собой функционально изолированные ветки API.

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

Ниже разобран сквозной практический сценарий: как подготовить 45-минутную запись двух собеседников с техническими терминами, преодолеть документированные лимиты API, выполнить распознавание обеих частей через официальный Python SDK, сформировать черновые структуры данных, скорректировать их по аудиозаписи и передать выверенные копии протоколисту или редактору субтитров.


Архитектурные ограничения и конфликт параметров

В качестве иллюстрации рассмотрим типовую рабочую встречу: тимлид Алексей и продакт-менеджер Михаил в течение 45 минут обсуждают архитектуру миграции и запуск сервисов с проектными именами DataPulse и CloudForge. Разговор сопровождается терминами, англицизмами, перебиваниями и оговорками.

При проектировании интеграции с Gemini 3.5 Transcribe необходимо учитывать три жестких правила:

  • Несовместимость словаря и структурных меток: Параметр custom_vocabulary (до 1000 выражений, практический оптимум — до 100) нельзя передавать вместе с diarization_mode или timestamp_granularities. Приходится выбирать: либо поручить модели распознавание редких брендов (отказавшись от автоматических спикеров и таймкодов), либо запросить детальную разметку реплик и времени, выверяя терминологию на этапе постобработки.
  • Несовместимость режима smart и временной шкалы: Режим smart выполняет речевую нормализацию: устраняет слова-паразиты, заикания и ложные старты, перестраивая синтаксис. Из-за удаления и перемещения токенов модель не может сопоставить итоговые слова с временной шкалой аудиопотока. Поэтому спикеры и пословные таймкоды в smart недоступны, а сам режим возвращает только сплошной текст (output_text), не создавая автоматически списков задач (Action Items).
  • 30-минутный лимит при расширенной разметке: Обычный запрос без детальных аннотаций принимает аудио продолжительностью до 60 минут. Однако при активации diarization_mode или timestamp_granularities предельная длина аудиофайла по документации составляет 30 минут. Запросы длиннее этого порога выходят за рамки поддерживаемой спецификации, и отправлять их в API не следует: длинное аудио необходимо заранее разделить на стороне клиента.

Подготовка и разделение 45-минутной записи

Для получения диаризации и пословных таймкодов 45-минутный исходник требуется разрезать на сегменты короче 30 минут. В рамках нашего гипотетического примера разделим запись на две части:

  • Часть 1: 00:00–25:00 (условно ровно 1500.0 секунд);
  • Часть 2: 25:00–45:00 (оставшиеся 20 минут, 1200.0 секунд).

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

При разделении возникают две проблемы непрерывности:

  1. Сброс временных меток во втором сегменте: Вторая часть обрабатывается API как самостоятельный файл, и отсчет пословных смещений в ней начинается с 0.000s. Чтобы восстановить единую шкалу встречи, ко всем временным меткам слов из второй части необходимо программно прибавить фактическую длительность первой части.
  2. Локальность меток спикеров: Модель присваивает идентификаторы spk_1, spk_2 изолированно внутри каждого вызова. Участник, получивший метку spk_1 в первом фрагменте, во втором может быть обозначен как spk_2. Автоматически объединять одинаковые технические метки из разных запросов нельзя — это приведет к путанице реплик. Для каждой части требуется независимая таблица сопоставления с реальными участниками по итогам выборочного прослушивания.

Варианты конфигурации для разных задач

Параметры распознавания передаются в объекте generation_config в поле transcription_config. Ниже приведены базовые конфигурации под разные сценарии.

Для связного нормализованного текста встречи без разметки спикеров и времени (файл короче 60 минут передается целиком):

generation_config = {
    "transcription_config": {
        "mode": "smart"
    }
}

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

generation_config = {
    "transcription_config": {
        "custom_vocabulary": ["DataPulse", "CloudForge", "ClickHouse", "gRPC"]
    }
}

Для задач протоколирования реплик и подготовки таймкодов под субтитры (действует лимит аудио до 30 минут):

generation_config = {
    "transcription_config": {
        "mode": {
            "type": "verbatim",
            "diarization_mode": "speaker",
            "timestamp_granularities": ["word"],
        }
    }
}

Полный рабочий конвейер на Python SDK: обработка двух частей

Скрипт выполняется еще до прослушивания аудиозаписи и ручной проверки: он загружает обе части встречи через Files API, отправляет запросы в Interactions API, извлекает пословные аннотации, валидирует таймкоды и спикеров, сдвигает временную шкалу второй части на 1500.0 секунд и записывает предварительные файлы:

  • meeting_transcript.txt — черновой связный диалог участников в кодировке UTF-8;
  • word_timestamps.json — черновой массив непрерывных пословных таймкодов для последующей нарезки субтитров.

Созданные скриптом файлы являются именно предварительными черновиками (drafts), а не выверенными документами. Читателю необходимо прослушать запись, сопоставить реплики с реальными голосами (speaker mapping), проверить корректность технической терминологии (terms) и временных смещений (time offsets), после чего скорректировать параметры в коде и перезапустить скрипт либо отредактировать полученные текстовые файлы вручную. Только после такой ручной сверки проверенные копии передаются протоколисту или редактору субтитров.

Скрипт генерирует промежуточные структуры данных для аналитиков и монтажеров; он не создает автоматически готовые Action Items или файлы формата .srt. Имена участников и временная граница 1500.0 секунд заданы для демонстрации гипотетического сценария.

import json
from google import genai

client = genai.Client()

audio_part1 = client.files.upload(file="meeting_part1.mp3")
audio_part2 = client.files.upload(file="meeting_part2.mp3")

transcription_mode_config = {
    "transcription_config": {
        "mode": {
            "type": "verbatim",
            "diarization_mode": "speaker",
            "timestamp_granularities": ["word"],
        }
    }
}

interaction_part1 = client.interactions.create(
    model="gemini-3.5-transcribe",
    input=[
        {
            "type": "audio",
            "uri": audio_part1.uri,
            "mime_type": audio_part1.mime_type,
        }
    ],
    generation_config=transcription_mode_config,
)

interaction_part2 = client.interactions.create(
    model="gemini-3.5-transcribe",
    input=[
        {
            "type": "audio",
            "uri": audio_part2.uri,
            "mime_type": audio_part2.mime_type,
        }
    ],
    generation_config=transcription_mode_config,
)

def extract_word_annotations(interaction):
    words = []
    for step in getattr(interaction, "steps", []) or []:
        for content in getattr(step, "content", []) or []:
            for annotation in getattr(content, "annotations", []) or []:
                if getattr(annotation, "type", None) == "word_info":
                    words.append(annotation)
    return words

def parse_offset_seconds(offset_val, word_text):
    if offset_val is None or offset_val == "":
        raise ValueError(f"Отсутствует таймкод для слова '{word_text}'. Требуется проверка аудиозаписи.")
    val_str = str(offset_val)
    if val_str.endswith("s"):
        val_str = val_str[:-1]
    try:
        return float(val_str)
    except ValueError:
        raise ValueError(f"Некорректный формат таймкода '{offset_val}' для слова '{word_text}'. Требуется проверка аудиозаписи.")

words_part1 = extract_word_annotations(interaction_part1)
words_part2 = extract_word_annotations(interaction_part2)

if not words_part1 or not words_part2:
    raise ValueError("Один из аудиосегментов не содержит пословных аннотаций. Пустой результат не может считаться успешным.")

part1_offset_seconds = 0.0
part2_offset_seconds = 1500.0

manual_mapping_part1 = {
    "spk_1": "Алексей",
    "spk_2": "Михаил",
}

manual_mapping_part2 = {
    "spk_1": "Михаил",
    "spk_2": "Алексей",
}

unified_word_stream = []

for word in words_part1:
    text = getattr(word, "text", "")
    raw_speaker = getattr(word, "speaker", None)
    if not raw_speaker or raw_speaker not in manual_mapping_part1:
        raise ValueError(f"Неизвестный спикер '{raw_speaker}' в части 1. Требуется ручная верификация по аудио.")
    resolved_speaker = manual_mapping_part1[raw_speaker]
    word_start = parse_offset_seconds(getattr(word, "start_offset", None), text) + part1_offset_seconds
    word_end = parse_offset_seconds(getattr(word, "end_offset", None), text) + part1_offset_seconds
    unified_word_stream.append({
        "text": text,
        "speaker": resolved_speaker,
        "start_seconds": word_start,
        "end_seconds": word_end,
        "part": 1,
    })

for word in words_part2:
    text = getattr(word, "text", "")
    raw_speaker = getattr(word, "speaker", None)
    if not raw_speaker or raw_speaker not in manual_mapping_part2:
        raise ValueError(f"Неизвестный спикер '{raw_speaker}' в части 2. Требуется ручная верификация по аудио.")
    resolved_speaker = manual_mapping_part2[raw_speaker]
    word_start = parse_offset_seconds(getattr(word, "start_offset", None), text) + part2_offset_seconds
    word_end = parse_offset_seconds(getattr(word, "end_offset", None), text) + part2_offset_seconds
    unified_word_stream.append({
        "text": text,
        "speaker": resolved_speaker,
        "start_seconds": word_start,
        "end_seconds": word_end,
        "part": 2,
    })

dialogue_turns = []
current_turn = None

for item in unified_word_stream:
    if current_turn is None or current_turn["speaker"] != item["speaker"] or current_turn["part"] != item["part"]:
        if current_turn is not None:
            dialogue_turns.append(current_turn)
        current_turn = {
            "part": item["part"],
            "speaker": item["speaker"],
            "start_seconds": item["start_seconds"],
            "end_seconds": item["end_seconds"],
            "words": [item["text"]],
        }
    else:
        current_turn["end_seconds"] = item["end_seconds"]
        current_turn["words"].append(item["text"])

if current_turn is not None:
    dialogue_turns.append(current_turn)

def format_timestamp(seconds):
    minutes = int(seconds // 60)
    remaining_seconds = seconds % 60
    return f"{minutes:02d}:{remaining_seconds:06.3f}"

with open("meeting_transcript.txt", "w", encoding="utf-8") as f_transcript:
    for turn in dialogue_turns:
        start_str = format_timestamp(turn["start_seconds"])
        end_str = format_timestamp(turn["end_seconds"])
        speech_text = " ".join(turn["words"])
        f_transcript.write(f"[{start_str} - {end_str}] {turn['speaker']}: {speech_text}\n")

with open("word_timestamps.json", "w", encoding="utf-8") as f_json:
    json.dump(unified_word_stream, f_json, ensure_ascii=False, indent=2)

Иллюстративный пример пословной структуры и зоны контроля

Ниже представлена синтетическая схема извлеченных данных на стыке двух реплик.

Примечание: Данный фрагмент служит исключительно иллюстрацией структуры возвращаемых объектов, а не реальным логом вызова API. Фактическое качество распознавания зависит от акустики, микрофонов и четкости речи.

[Строка 1] [spk_1] (0.100s -> 0.420s) Мы
[Строка 2] [spk_1] (0.450s -> 0.810s) переносим
[Строка 3] [spk_1] (0.830s -> 1.250s) ДатаПульс
[Строка 4] [spk_1] (1.300s -> 1.550s) на
[Строка 5] [spk_1] (1.600s -> 2.100s) CloudForge.
[Строка 6] [spk_1] (2.300s -> 2.600s) Да,
[Строка 7] [spk_2] (2.650s -> 2.900s) согласен,
[Строка 8] [spk_2] (2.950s -> 3.400s) э-э-э,
[Строка 9] [spk_2] (3.420s -> 3.900s) логично.

На что обращать внимание при ручной вычитке:

  1. Привязка меток к участникам (Строки 1–5 и 7–9): Идентификаторы spk_1 и spk_2 — лишь порядковые номера голосов. Редактор прослушивает первые секунды каждого сегмента и фиксирует соответствие: например, в Части 1 метка spk_1 принадлежит Алексею, а spk_2 — Михаилу.
  2. Смена реплик и короткие реакции (Строка 6): Слово «Да,» приписано к spk_1. В динамичном разговоре это может быть поддерживающим междометием собеседника (spk_2). Границы смены говорящих требуют выборочной сверки с аудио.
  3. Фонетическое написание терминов (Строка 3): При отключенном словаре англоязычный бренд может транскрибироваться кириллицей (ДатаПульс). Редактор приводит такие вхождения к эталонному виду DataPulse.
  4. Недопустимость неизбирательной автозамены: Любые терминологические правки вносятся точечно с учетом контекста фразы. Глобальная замена по всему тексту рискует исказить схожие по звучанию общеупотребительные слова или цитаты.
  5. Сорные звуки (Строка 8): В режиме verbatim фиксируются паузы хезитации (э-э-э). В чистовом протоколе их удаляют, а при монтаже субтитров сохраняют тайминг, если важно совпадение с артикуляцией в кадре.

Регламент выборочной проверки перед передачей материала

Сырой результат программного объединения проходит обязательный экспресс-аудит по трем контрольным точкам:

  1. Проверка спикеров и границ реплик: Выборочно прослушать 15–20 секунд на 2–3 стыках смены собеседников. Убедиться, что голоса не слиплись в одного участника и монолог не раздробился на ложные метки. При участии трех и более человек разделение по документации носит экспериментальный характер и требует более плотного контроля.
  2. Проверка терминов, числовых значений и сущностей: Сформировать список ключевых названий (DataPulse, CloudForge, номера версий, бюджеты). Найти их в тексте через поиск и сверить проблемные места с фонограммой, избегая слепых автозамен.
  3. Проверка непрерывности временной шкалы: Сверить первую минуту первой части и стык перехода на вторую часть (сразу после отметки 25:00 / 1500.0 секунд). Убедиться, что таймкоды второго сегмента плавно продолжают первый, а не обнулились к началу шкалы.

Передача результатов смежникам и условия остановки процесса

Автоматически сгенерированные скриптом файлы meeting_transcript.txt и word_timestamps.json записываются еще до прослушивания аудиозаписи и являются предварительными черновиками. Передавать их в работу в исходном виде нельзя: сначала необходимо выполнить регламент ручной проверки, сопоставить спикеров, термины и временные смещения с аудиозаписью, скорректировать данные (перезапустив скрипт с обновленными параметрами или отредактировав файлы напрямую), и лишь затем передавать выверенные копии смежным специалистам:

  • Для протоколиста или аналитической модели (Note Taker / LLM-суммаризация): Передается выверенная копия стенограммы — проверенный текст с подтвержденной атрибуцией спикеров и скорректированной терминологией. Он служит фундаментом для составления резюме встречи и выделения договоренностей, но сам по себе не является готовым списком Action Items.
  • Для монтажера или редактора субтитров (Caption Editor): Передается выверенная копия массива пословных таймкодов — проверенный массив слов с непрерывными временными метками. Конкретные параметры разбиения на строки (хронометраж экрана, допустимая длина строки, скорость чтения) определяются целевой платформой, плеером и языком, после чего специализированный конвертер формирует финальные файлы .srt или .vtt.

Условия остановки процесса (Stop Conditions)

Не передавайте файлы на следующий этап, если зафиксировано хотя бы одно из условий:

  1. Аудиозапись длительностью свыше 30 минут была передана на диаризацию или пословные таймкоды без разделения на сегменты (запрос превышает документальный лимит, отправлять его в API нельзя).
  2. Черновые файлы meeting_transcript.txt или word_timestamps.json переданы смежникам сразу после работы скрипта без выборочного прослушивания аудиозаписи и ручной проверки.
  3. Спикеры во второй части сопоставлены вслепую по номерам меток первой части без прослушивания аудиозаписи, либо в потоке остались неразрешенные идентификаторы.
  4. Ко второму сегменту не добавлено фактическое временное смещение первого фрагмента, либо в данных обнаружены пропущенные таймкоды слов.
  5. В документе была применена неизбирательная сквозная автозамена терминов без контекстной проверки, либо ключевые параметры и цифровые данные не сверены с записью.

Готовы оптимизировать LLM workflow?

Подключите единый API, управляйте ключами и контролируйте расходы на AI-модели в BetterToken.

Начать бесплатно