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

Превращение аудиозаписи рабочей встречи в надежный рабочий документ требует четкого выбора архитектурных параметров распознавания. В 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.
При разделении возникают две проблемы непрерывности:
- Сброс временных меток во втором сегменте: Вторая часть обрабатывается API как самостоятельный файл, и отсчет пословных смещений в ней начинается с
0.000s. Чтобы восстановить единую шкалу встречи, ко всем временным меткам слов из второй части необходимо программно прибавить фактическую длительность первой части. - Локальность меток спикеров: Модель присваивает идентификаторы
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–5 и 7–9): Идентификаторы
spk_1иspk_2— лишь порядковые номера голосов. Редактор прослушивает первые секунды каждого сегмента и фиксирует соответствие: например, в Части 1 меткаspk_1принадлежит Алексею, аspk_2— Михаилу. - Смена реплик и короткие реакции (Строка 6): Слово «Да,» приписано к
spk_1. В динамичном разговоре это может быть поддерживающим междометием собеседника (spk_2). Границы смены говорящих требуют выборочной сверки с аудио. - Фонетическое написание терминов (Строка 3): При отключенном словаре англоязычный бренд может транскрибироваться кириллицей (
ДатаПульс). Редактор приводит такие вхождения к эталонному видуDataPulse. - Недопустимость неизбирательной автозамены: Любые терминологические правки вносятся точечно с учетом контекста фразы. Глобальная замена по всему тексту рискует исказить схожие по звучанию общеупотребительные слова или цитаты.
- Сорные звуки (Строка 8): В режиме
verbatimфиксируются паузы хезитации (э-э-э). В чистовом протоколе их удаляют, а при монтаже субтитров сохраняют тайминг, если важно совпадение с артикуляцией в кадре.
Регламент выборочной проверки перед передачей материала
Сырой результат программного объединения проходит обязательный экспресс-аудит по трем контрольным точкам:
- Проверка спикеров и границ реплик: Выборочно прослушать 15–20 секунд на 2–3 стыках смены собеседников. Убедиться, что голоса не слиплись в одного участника и монолог не раздробился на ложные метки. При участии трех и более человек разделение по документации носит экспериментальный характер и требует более плотного контроля.
- Проверка терминов, числовых значений и сущностей:
Сформировать список ключевых названий (
DataPulse,CloudForge, номера версий, бюджеты). Найти их в тексте через поиск и сверить проблемные места с фонограммой, избегая слепых автозамен. - Проверка непрерывности временной шкалы: Сверить первую минуту первой части и стык перехода на вторую часть (сразу после отметки 25:00 / 1500.0 секунд). Убедиться, что таймкоды второго сегмента плавно продолжают первый, а не обнулились к началу шкалы.
Передача результатов смежникам и условия остановки процесса
Автоматически сгенерированные скриптом файлы meeting_transcript.txt и word_timestamps.json записываются еще до прослушивания аудиозаписи и являются предварительными черновиками. Передавать их в работу в исходном виде нельзя: сначала необходимо выполнить регламент ручной проверки, сопоставить спикеров, термины и временные смещения с аудиозаписью, скорректировать данные (перезапустив скрипт с обновленными параметрами или отредактировав файлы напрямую), и лишь затем передавать выверенные копии смежным специалистам:
- Для протоколиста или аналитической модели (Note Taker / LLM-суммаризация): Передается выверенная копия стенограммы — проверенный текст с подтвержденной атрибуцией спикеров и скорректированной терминологией. Он служит фундаментом для составления резюме встречи и выделения договоренностей, но сам по себе не является готовым списком Action Items.
- Для монтажера или редактора субтитров (Caption Editor): Передается выверенная копия массива пословных таймкодов — проверенный массив слов с непрерывными временными метками. Конкретные параметры разбиения на строки (хронометраж экрана, допустимая длина строки, скорость чтения) определяются целевой платформой, плеером и языком, после чего специализированный конвертер формирует финальные файлы
.srtили.vtt.
Условия остановки процесса (Stop Conditions)
Не передавайте файлы на следующий этап, если зафиксировано хотя бы одно из условий:
- Аудиозапись длительностью свыше 30 минут была передана на диаризацию или пословные таймкоды без разделения на сегменты (запрос превышает документальный лимит, отправлять его в API нельзя).
- Черновые файлы
meeting_transcript.txtилиword_timestamps.jsonпереданы смежникам сразу после работы скрипта без выборочного прослушивания аудиозаписи и ручной проверки. - Спикеры во второй части сопоставлены вслепую по номерам меток первой части без прослушивания аудиозаписи, либо в потоке остались неразрешенные идентификаторы.
- Ко второму сегменту не добавлено фактическое временное смещение первого фрагмента, либо в данных обнаружены пропущенные таймкоды слов.
- В документе была применена неизбирательная сквозная автозамена терминов без контекстной проверки, либо ключевые параметры и цифровые данные не сверены с записью.