Aller au contenu
Video2Any
2026-08-22

Huit bugs qui avaient l’air de venir de l’algorithme

Video2Any retrouve les diapositives d’un enregistrement en décodant les images dans le navigateur, en comparant chacune à la précédente et en gardant celles qui ont assez changé. Quand le résultat est mauvais, le suspect évident, c’est cette comparaison. Ce n’est presque jamais elle.

Voici huit choses que nous avons traquées en une semaine. Deux venaient du détecteur. Le reste : une garde restée en place, un script de test qui mentait, une liste virtualisée, une course avec un appel d’API, un plafond de mémoire, et un log d’erreur qui jetait la seule partie utile de lui-même.

Les huit

1. Le réglage qui ne servait à rien

On nous signale qu’une vidéo courte donne trois diapositives en Dense, comme en Par défaut. Nous avons passé un moment sur la formule du seuil avant de vérifier le plus élémentaire : est-ce que le réglage était seulement appliqué ?

Il ne l’était pas. Le premier balayage est protégé par une ref pour ne tourner qu’une fois par fichier. Changer la densité reconstruisait le callback, mais la garde renvoyait l’effet aussitôt, et la seule chose qui relançait vraiment était un bouton dans un avertissement sans rapport. Après le changement, on voyait le résultat précédent, aux mêmes horodatages. Sur ce clip, Clairsemée / Par défaut / Dense donnent maintenant 3, 6 et 13. Avant, les trois donnaient 6.

2. Le banc de test a menti deux fois

Pour expliquer un résultat bizarre, nous avons refait le pipeline hors ligne en Node, avec le vrai fichier, et les chiffres ne collaient pas avec l’application — dans les deux sens : d’abord 1 diapositive côté script contre 6 côté app, puis 15 contre 6 après correction.

Deux causes. Le script extrayait les images avec ffmpeg en niveaux de gris, alors que le navigateur donne au moteur du RGBA issu d’un canvas, et l’écart entre les deux redimensionnements suffit à faire passer le seuil calibré de 0,618 à 0,650. Et il sautait la fonction qui décide d’utiliser ou non le masque d’activité : il ne suivait donc pas le chemin que suit l’application. Un banc de test qui reproduit l’algorithme mais pas le pipeline vous donnera une mauvaise réponse avec beaucoup d’assurance.

3. Le DOM ne contenait que ce qui était affiché

Un test vérifiait qu’une vidéo de 32 minutes produisait des diapositives au-delà de la 30ᵉ. Il lisait l’horodatage de toutes les vignettes et trouvait la dernière à 27:00, exactement l’allure d’une analyse tronquée.

La grille est virtualisée : seules les lignes visibles existent en tant qu’éléments. En descendant d’abord tout en bas, la vraie dernière était à 32:00. L’assertion mesurait la fenêtre d’affichage depuis le début.

4. Chaque worker dédoublonnait dans son coin

Les vidéos longues sont balayées en morceaux parallèles. Chaque worker écarte les plans répétés à l’intérieur de son propre morceau — quelqu’un qui revient à une diapositive précédente, une visio qui recoupe sur le même cadrage — et aucun ne voit ce que font les autres.

Un plan qui revient survivait donc une fois par morceau où il apparaissait. Sur une réunion de 29 minutes, cela faisait trois diapositives en double sur neuf. Et cela rendait le résultat dépendant du nombre de morceaux, c’est-à-dire du nombre de cœurs de la machine en face. La vérification est passée sur le thread principal, au fil de l’arrivée de chaque diapositive : un doublon n’apparaît donc jamais pour disparaître ensuite.

5. Le meilleur design a donné un moins bon résultat

Que chaque morceau calibre son propre seuil sur sa propre tranche est manifestement bancal : une vidéo, une distribution, un seuil. Nous l’avons donc écrit. Les workers envoient les ratios mesurés, le thread principal les met en commun, calibre une fois et renvoie la réponse.

L’écart entre quatre morceaux et huit est passé de trois diapositives à une. Et le résultat s’est dégradé. Sur cette même réunion de 29 minutes, où la détection de scènes de ffmpeg compte sept coupes réelles, le calibrage par morceau plus la déduplication ci-dessus donne six, et le calibrage global donne quatre. En mettant tout en commun, la distribution s’élargit, la médiane plus deux MAD monte, et de vraies transitions tombent. Nous l’avons annulé. Un design peut être plus juste et perdre quand même contre celui que vous avez, et la seule façon de le savoir est d’avoir quelque chose hors de votre code pour vérifier.

6. Faux parce que nous n’avions pas encore demandé

Un abonné payant a envoyé un long enregistrement et n’a obtenu que les trente premières minutes. En appuyant sur Rééchantillonner, tout arrivait — d’où l’impression d’un problème de détection.

L’éditeur démarre avant de savoir qui vous êtes. L’indicateur d’abonnement commence à faux, non pas parce que la réponse était non, mais parce que la requête était encore en route, et la limite de durée a lu ce faux et appliqué le plafond de l’offre gratuite. Au moment du rééchantillonnage, l’offre était arrivée. Nous avons d’abord corrigé en l’attendant, puis supprimé l’attente : le worker a déjà la session au moment de servir la page, il écrit donc l’offre dans le document et le premier rendu la connaît.

7. Toutes les erreurs s’appelaient « Error »

La transcription échouait plus souvent qu’elle ne réussissait, six échecs pour cinq réussites, et toutes étaient enregistrées sous la chaîne « Error ». Les dix-neuf extractions en échec aussi.

Le code enregistrait err.name, qui vaut « Error » pour toute erreur construite à la main. Le message, là où l’information a toujours été, n’allait nulle part. Une fois enregistré, l’échec a eu un mécanisme : decodeAudioData rééchantillonne à la fréquence du contexte audio, donc décoder un cours de 55 minutes au 48 kHz stéréo du matériel matérialise environ 630 Mo d’échantillons flottants avant toute autre chose, et deux heures font 1,4 Go. En décodant sur un contexte déjà à 16 kHz, le modèle reçoit exactement le même audio pour environ un sixième de la mémoire.

8. C’était rapide et ça donnait quand même l’impression d’être cassé

Un enregistrement de réunion de 92 minutes se balaie en 52 secondes. La première diapositive apparaît à la 50ᵉ.

Rien n’était lent. Le seuil se calibre sur toute la distribution des différences entre images : aucune diapositive ne peut donc être choisie avant que la vidéo ait été lue de bout en bout, et pendant ces 50 secondes il n’y avait qu’un pourcentage à l’écran. Un pourcentage qui avance sans rien à côté se lit comme un blocage. Il indique maintenant quelle moitié du travail tourne et quelle part de la vidéo a été lue, et les mêmes 52 secondes ont cessé d’être un reproche.

Ce qu’on dirait à celui qu’on était il y a une semaine

Vérifiez que l’entrée est bien arrivée jusqu’au code avant de raisonner sur le code. La moitié de ces cas, c’était un réglage jamais arrivé, une réponse pas encore revenue, ou un test qui regardait autre chose.

Et gardez quelque chose d’extérieur à votre pipeline pour vérifier. La détection de scènes de ffmpeg n’est pas ce que nous construisons, et n’a pas à l’être : il suffit qu’elle soit indépendante. Sans elle, nous aurions publié la réécriture du calibrage, parce que c’était le meilleur design, et perdu discrètement trois vraies diapositives sur sept.

Convertir une vidéoBlog