Oito bugs que pareciam ser do algoritmo
O Video2Any acha os slides de uma gravação decodificando quadros no navegador, comparando cada um com o anterior e guardando os que mudaram o suficiente. Quando o resultado sai errado, o suspeito óbvio é essa comparação. Quase nunca é.
São oito coisas que caçamos em uma semana. Duas estavam no detector. O resto: uma guarda que ficou para trás, um script de teste que mentia, uma lista virtualizada, uma corrida com uma chamada de API, um teto de memória e um log de erro que jogava fora a única parte útil de si mesmo.
Os oito
1. O controle que não fazia nada
Chegou um relato de que um vídeo curto dava três slides no Denso, igual ao Padrão. Ficamos um tempo olhando a fórmula do limiar antes de checar o mais básico: o ajuste estava sequer sendo aplicado?
Não estava. A primeira varredura é protegida por uma ref para rodar uma vez por arquivo. Trocar a densidade reconstruía o callback, mas a guarda devolvia o efeito na hora, e a única coisa que de fato reexecutava era um botão de um aviso sem relação nenhuma. O que você via depois de trocar era o resultado anterior, com os mesmos timestamps. Agora, no mesmo clipe, Esparso / Padrão / Denso dão 3, 6 e 13. Antes os três davam 6.
2. O script de teste mentiu duas vezes
Para explicar um resultado estranho, refizemos o pipeline offline em Node, jogamos o arquivo real e os números não bateram com o app — e nas duas vezes em direções opostas: primeiro o script dizia 1 slide e o app 6; depois de corrigir, o script dizia 15 e o app 6.
Duas causas. O script tirava os quadros com ffmpeg em escala de cinza, enquanto o navegador entrega ao motor o RGBA de um canvas, e a diferença entre os dois redimensionamentos já move o limiar calibrado de 0,618 para 0,650. Além disso, ele pulava a função que decide se usa a máscara de atividade, então não passava pelo caminho por onde o app passa. Um script que reproduz o algoritmo mas não o pipeline te dá uma resposta errada com a maior convicção.
3. No DOM só tinha o que estava na tela
Um teste verificava se um vídeo de 32 minutos produzia slides depois dos 30. Ele lia o timestamp de todas as miniaturas e o último dava 27:00, exatamente a cara de uma análise truncada.
A grade é virtualizada: só as linhas visíveis existem como elementos. Rolando até o fim antes de ler, o último de verdade estava em 32:00. A asserção estava medindo o viewport esse tempo todo.
4. Cada worker tirava duplicatas sozinho
Vídeos longos são varridos em pedaços paralelos. Cada worker descarta as cenas repetidas dentro do seu próprio pedaço — alguém voltando para um slide anterior, uma chamada cortando de volta para o mesmo enquadramento — e nenhum enxerga o dos outros.
Então uma cena que reaparece sobrevivia uma vez por pedaço em que aparecia. Numa reunião de 29 minutos, eram três slides duplicados de nove. E ainda fazia o resultado depender de quantos pedaços existiam, ou seja, de quantos núcleos a máquina de quem usa tem. A checagem foi para a thread principal e roda conforme cada slide chega, então uma duplicata nunca chega a aparecer para depois sumir.
5. O design mais certo saiu pior
Cada pedaço calibrar o próprio limiar no próprio trecho é claramente estranho: um vídeo, uma distribuição, um limiar. Então fizemos assim. Os workers mandam as proporções que mediram, a thread principal junta, calibra uma vez e devolve a resposta.
A diferença entre quatro pedaços e oito caiu de três slides para um. E o resultado piorou. Na mesma reunião de 29 minutos, onde a detecção de cenas do próprio ffmpeg conta sete cortes reais, calibrar por pedaço mais a deduplicação acima dá seis e calibrar globalmente dá quatro. Juntando tudo a distribuição alarga, a mediana mais dois MAD sobe e transições de verdade caem fora. Revertemos. Um design pode ser mais correto e ainda assim perder para o que você já tem, e o único jeito de saber é ter algo fora do seu código para comparar.
6. Falso porque a gente ainda não tinha perguntado
Um assinante pagante subiu uma gravação longa e recebeu só os primeiros trinta minutos. Apertando Reamostrar vinha inteira, que é justamente o que fazia aquilo parecer problema de detecção.
O editor começa antes de saber quem é você. A flag de assinatura começa falsa — não porque a resposta foi não, e sim porque a requisição ainda estava a caminho — e o limite de duração leu esse falso e aplicou o teto do plano grátis. Quando a reamostragem rodava, o plano já tinha chegado. Primeiro consertamos esperando por ele; depois tiramos a espera: o worker já tem a sessão na hora de servir a página, então escreve o plano no documento e a primeira renderização já sabe.
7. Todo erro se chamava “Error”
A transcrição falhava mais do que funcionava, seis falhas contra cinco sucessos, e todas eram registradas como a string “Error”. As dezenove extrações que falharam, idem.
O código gravava err.name, que vale “Error” para qualquer erro construído na mão. A mensagem, onde a informação sempre esteve, não ia para lugar nenhum. Quando passamos a gravá-la, a falha ganhou um mecanismo: decodeAudioData reamostra para a taxa do contexto de áudio, então decodificar uma aula de 55 minutos nos 48 kHz estéreo do hardware materializa uns 630 MB de amostras em ponto flutuante antes de qualquer outra coisa, e duas horas dão 1,4 GB. Decodificando num contexto que já está em 16 kHz, o modelo recebe o mesmo áudio com cerca de um sexto da memória.
8. Era rápido e mesmo assim parecia quebrado
Uma gravação de reunião de 92 minutos é varrida em 52 segundos. O primeiro slide aparece no segundo 50.
Não havia nada lento. O limiar é calibrado a partir da distribuição inteira das diferenças entre quadros, então nenhum slide pode ser escolhido antes de o vídeo ter sido lido do começo ao fim, e nesses 50 segundos a única coisa na tela era uma porcentagem. Uma porcentagem andando sozinha, sem nada ao lado, se lê como travamento. Agora ela diz qual metade do trabalho está rodando e quanto do vídeo já foi lido, e os mesmos 52 segundos deixaram de ser reclamação.
O que a gente diria para quem éramos uma semana atrás
Confirme que a entrada chegou no código antes de raciocinar sobre o código. Metade destes foram ajustes que nunca chegaram, respostas que ainda não tinham voltado ou um teste olhando para outra coisa.
E mantenha algo fora do seu pipeline para comparar. A detecção de cenas do ffmpeg não é o que estamos construindo e nem precisa ser: só precisa ser independente. Sem ela teríamos publicado a reescrita da calibração, porque era o design melhor, e perdido em silêncio três slides reais de sete.