Timestamps

Timestamp por palavra: o que dá para construir com isso

Cada palavra volta com início e fim em segundos decimais, com erro mediano de 69 a 86 ms. Busca dentro do áudio, corte de clipe e destaque sincronizado saem daí.

Transcrição que devolve só o texto resolve metade dos problemas. A outra metade precisa saber onde cada palavra está no áudio, e é isso que o campo words entrega:

"words": [
	{ "start": 0.32, "end": 0.78, "text": "Bom", "speaker": null },
	{ "start": 0.78, "end": 1.04, "text": "dia", "speaker": null },
	{ "start": 1.04, "end": 1.51, "text": "a", "speaker": null },
	{ "start": 1.51, "end": 2.06, "text": "todos.", "speaker": null }
]

start e end são segundos, em número decimal, contados do começo do áudio. 1.51 é um segundo e meio, não 1510. Vale conferir isso primeiro, porque uma boa parte das bibliotecas de mídia espera milissegundos inteiros e o erro não aparece: o player só sincroniza mal.

Quão preciso é

Medimos o erro contra referência externa, casando as palavras pelo texto:

MedidaValor
Erro mediano (p50)69 a 86 ms
Palavras com desvio acima de 1 s, num podcast1,8%
Palavras com desvio acima de 1 s, num vlog com música3,1%

O número mediano é fácil de traduzir: a 24 quadros por segundo, um quadro dura 41,7 ms. O erro típico é menos de dois quadros. Ninguém enxerga isso numa legenda nem ouve num corte.

O número que importa para o seu produto é o outro. Entre 1,8% e 3,1% das palavras saem com mais de um segundo de desvio, e a fatia pior é justamente áudio com música. Um produto que corta clipe automaticamente e publica sem revisão vai errar nessa fatia. Um produto que usa o timestamp para levar o usuário até o trecho, não. A diferença é se o erro custa um segundo de rolagem ou um vídeo publicado errado.

Os números por fatia estão em precisão e qualidade.

Busca que leva ao minuto certo

É o uso mais direto. Indexando as palavras com o tempo, o resultado da busca para de ser "este episódio fala sobre isso" e passa a ser "fala sobre isso aos 34min12".

O básico é achar a sequência de palavras que casa com o termo e devolver o start da primeira:

function locate(words: Word[], query: string) {
	const terms = query.toLowerCase().split(/\s+/);
	for (let i = 0; i <= words.length - terms.length; i++) {
		const hit = terms.every((term, j) => words[i + j].text.toLowerCase().replace(/[.,!?]/g, "") === term);
		if (hit) return { start: words[i].start, end: words[i + terms.length - 1].end };
	}
	return null;
}

Na hora de tocar, tire uns dois segundos do start. O trecho começa no meio de uma frase se você pular exatamente na palavra, e o ouvinte perde o contexto.

Destaque sincronizado com a reprodução

Aquele efeito de a palavra acender enquanto o áudio toca é uma busca binária no timeupdate:

function activeIndex(words: Word[], time: number) {
	let low = 0;
	let high = words.length - 1;
	while (low <= high) {
		const mid = (low + high) >> 1;
		if (time < words[mid].start) high = mid - 1;
		else if (time > words[mid].end) low = mid + 1;
		else return mid;
	}
	return high; // entre duas palavras: mantém a última que já passou
}

O return high no fim é o detalhe que separa um destaque estável de um que pisca. O fim de uma palavra não é o começo da seguinte: existe silêncio entre elas, e num intervalo desses a busca não acha nada. Devolver a última palavra que já terminou mantém o destaque parado durante a pausa, que é o comportamento que o olho espera.

Cortar áudio e vídeo na palavra

Um par de timestamps é um comando de corte:

ffmpeg -i episodio.mp3 -ss 2412.31 -to 2447.88 -c copy clipe.mp3

Duas recomendações que economizam retrabalho. Corte na pausa, não na fronteira exata da palavra: procure o maior intervalo entre end de uma palavra e start da seguinte dentro de meio segundo do ponto que você quer, e corte ali. E dê uma folga de 100 a 200 ms de cada lado, que é maior que o erro típico e não é audível.

O que os timestamps não resolvem

Sobreposição. Quando duas pessoas falam ao mesmo tempo, o trecho vira uma sequência só de palavras. Os tempos continuam, mas a atribuição de quem falou não: a diarização tem DER de 14,8% nessa fatia contra 4,6% no geral.

Música. Letra de música às vezes é transcrita como fala, e é onde o desvio acima de um segundo aparece mais (3,1% das palavras num vlog com trilha).

Reprodutibilidade. A saída não é determinística. Mandar o mesmo arquivo duas vezes pode devolver segmentação diferente em algumas dezenas de blocos. Se o seu produto guarda um clipe cortado por timestamp, guarde os números que você usou, não regere a transcrição para conferir. Não existe seed.

Alinhamento com legenda existente. A API transcreve o áudio; ela não recebe um texto pronto e devolve os tempos dele. Se você já tem o roteiro e precisa alinhar, o caminho é transcrever e casar as duas sequências de palavras do seu lado.

Com falante

Pedindo speakers, cada palavra ganha um rótulo ("A", "B") junto com os tempos. Aí dá para montar turnos e não só palavras, que é o que legenda de entrevista e ata de reunião precisam. A tarifa muda para US$ 0,05 por hora, e o detalhamento está em transcrições.

Sem speakers, speaker vem null em todas as palavras. Não é erro: é a resposta a uma pergunta que você não fez.

Todos os posts