Reuniões

Como transcrever reunião por API e virar ata com quem falou

Da gravação ao texto separado por falante, com o código que agrupa palavras em turnos. E onde a separação erra em reunião de verdade, que é sempre no mesmo lugar.

Gravação de reunião é o pior áudio que um sistema de transcrição recebe. Microfone longe, gente falando por cima, participante em rede ruim, cadeira arrastando. Ainda assim é o caso de uso mais pedido, porque ninguém quer escrever ata.

O caminho da gravação até a ata tem três partes: mandar o áudio pedindo separação de falantes, agrupar as palavras em turnos, e saber quais partes do resultado você pode confiar sem revisão.

Mandar a gravação

Se a gravação já está num bucket com URL pública, uma requisição resolve. Se ela está na sua máquina ou num bucket privado, sobe pelo upload e você usa o uploadId no lugar da url. Os dois caminhos estão em envio de arquivos.

curl https://api.transcrevo.com/v1/transcripts \
  -H "Authorization: Bearer $TRANSCREVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/reuniao.mp3",
    "speakers": 5,
    "language": "pt",
    "webhookUrl": "https://seu-servidor.com/hooks/transcrevo"
  }'

Três coisas nessa requisição merecem explicação.

speakers: 5 só deve ser informado quando você sabe. Numa reunião você normalmente sabe: o convite tem a lista de participantes. E aí informar ajuda, porque a contagem automática acerta o número exato em 146 de 216 arquivos medidos. Mas chute errado é pior que não chutar: ele força a separação para a contagem que você inventou, e o sistema passa a dividir uma voz em duas. Quando você não tem a lista, mande true e deixe a decisão com quem está com o áudio.

language: "pt" evita que a detecção automática decida sozinha. Em reunião com abertura curta ou muito ruído, informar o idioma erra menos.

webhookUrl existe porque reunião é longa. Uma gravação de duas horas leva minutos para processar, e perguntar de três em três segundos são umas cem requisições só para ouvir processing.

De palavras para turnos

A resposta vem como lista de palavras, cada uma com o rótulo do falante. Ata se escreve em turnos. A conversão é um reduce:

type Word = { start: number; end: number; text: string; speaker: string | null };
type Turn = { speaker: string | null; start: number; end: number; text: string };

function turns(words: Word[]): Turn[] {
	return words.reduce<Turn[]>((acc, word) => {
		const last = acc.at(-1);
		if (last !== undefined && last.speaker === word.speaker && word.start - last.end < 2) {
			last.text += ` ${word.text}`;
			last.end = word.end;
			return acc;
		}
		acc.push({ speaker: word.speaker, start: word.start, end: word.end, text: word.text });
		return acc;
	}, []);
}

A folga de 2 segundos é o que impede que uma pausa para respirar vire dois turnos da mesma pessoa. Aumente para reunião com muita hesitação, diminua para conversa rápida.

Com isso na mão a ata vira formatação:

[00:04:12] A: Então o prazo do fornecedor mudou para dezembro.
[00:04:31] C: Isso muda o cronograma da integração inteira?
[00:04:36] A: Muda a data de entrega, não o escopo.

Os rótulos A e B são anônimos por natureza: significam "primeira voz" e "segunda voz", nunca nomes. Amarrar rótulo a pessoa vem de fora do áudio. Numa reunião gravada por uma ferramenta que registra ordem de entrada, ou numa que grava uma faixa por participante, a amarração é confiável. Sem isso, o mais honesto é deixar A e B na ata e o revisor batizar.

Onde isso erra em reunião de verdade

Medimos a diarização em 216 arquivos, 19,6 horas de fala com anotação humana. DER de 4,6% sem colar, 2,8% com colar de 0,25 s. Esse é o número bom. Os dois números ruins são exatamente os que reunião produz o dia inteiro:

SituaçãoAcerto
Turno acima de 15 segundos95 a 99%
Turno abaixo de 5 segundos59 a 75%
Fala sobrepostaDER de 14,8%, contra 4,6% no geral

A leitura prática disso é direta. A ata de conteúdo funciona. Quem defendeu o quê, qual foi a decisão, quem ficou com a tarefa: são blocos longos de fala, e o sistema acerta entre 95 e 99% deles.

A métrica de comportamento não funciona. Contar quantas vezes cada pessoa falou, quem interrompeu quem, quanto tempo cada um ocupou: essas contas vivem justamente dos "uhum", "sim", "concordo" de dois segundos, que é onde o acerto cai para a faixa de 59 a 75%. Um produto que promete esse tipo de análise a partir de gravação de sala está prometendo o que a fatia mais difícil do problema não entrega. O detalhamento completo, com JER e falso alarme separados, está em precisão e qualidade.

E quando duas pessoas falam ao mesmo tempo, o trecho recebe um rótulo só. Numa discussão acalorada, esse é o pedaço que o revisor vai reescrever.

O que melhora o resultado antes de sair da gravação

  • Uma faixa por participante, quando a ferramenta de reunião permite. Aí não existe problema de diarização: você transcreve cada faixa separada e sabe de quem é. Custa o mesmo, porque a cobrança é por hora de áudio, e cinco faixas de uma hora são cinco horas.
  • glossary com os nomes da empresa. Nome próprio, marca e jargão são o erro mais comum de qualquer sistema de transcrição, porque são as palavras que menos aparecem em treino. Uma reunião interna é feita disso: nome de projeto, nome de cliente, sigla. Mande { "o que ele ouve": "o que escrever" }, até 100 entradas, e a correção vale só para aquele áudio.
  • Áudio da sala com um microfone só é o pior caso. Não há truque de API que resolva distância de microfone.

Guardar e apagar

Gravação de reunião costuma ter política de retenção, e ata de RH ou jurídico quase sempre tem. DELETE /v1/transcripts/{id} apaga o texto, as palavras e o áudio guardado; o registro de uso continua, porque é dele que sai a fatura, mas o conteúdo some e não volta. Transcrição ainda em processing não pode ser apagada: o resultado chegaria depois e repovoaria o que você apagou.

Vale lembrar que a saída não é reproduzível. Mandar o mesmo arquivo duas vezes pode devolver transcritos ligeiramente diferentes, e não existe seed. Para auditar uma reclamação sobre uma ata, guarde o transcrito que você entregou em vez de gerar de novo.

A conta

US$ 0,05 por hora com separação de falantes, proporcional ao segundo. Uma reunião semanal de uma hora, o ano inteiro, custa US$ 2,60. Cem reuniões por mês numa empresa média, digamos 150 horas, custam US$ 7,50 por mês. É barato o suficiente para a decisão passar a ser sobre revisão humana, não sobre o preço da transcrição. Os limites e o desconto acima de 10.000 horas estão em preços e limites.

Todos os posts