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 problèmes que nous avons traqués en une semaine. Deux seulement venaient du détecteur. Les six autres : une garde restée en place, un script de test qui donnait de mauvais chiffres, une liste virtualisée, une course avec un appel d’API, la mémoire arrivée à sa limite, et un journal d’erreurs. Ce dernier ne conservait de chaque erreur que la partie qui ne sert à rien.

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 : ce réglage arrivait-il jusqu’au détecteur ?
Il n’y arrivait 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 le balayage était un bouton situé dans un avertissement sans rapport. Après le changement, vous voyiez le résultat précédent, aux mêmes horodatages. Sur ce clip, Clairsemée, Par défaut et Dense donnent maintenant 3, 6 et 13. Avant, les trois donnaient 6.
2. Le script de test s’est trompé deux fois
Pour expliquer un résultat bizarre, nous avons refait le pipeline hors ligne en Node et nous lui avons donné le vrai fichier. Les chiffres ne collaient pas avec l’application, et les deux fois l’écart allait dans un sens opposé : d’abord 1 diapositive côté script contre 6 côté application, puis 15 contre 6 après correction.
Il y avait 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. Le script sautait aussi 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 script qui reproduit l’algorithme mais pas le pipeline vous donne 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ᵉ minute. Ce test lisait l’horodatage de toutes les vignettes et annonçait 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 tout en bas avant de lire, la vraie dernière était à 32:00. Cette assertion mesurait la fenêtre d’affichage, pas les données.
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 worker ne voit ce que font les autres.
Un plan qui revenait survivait donc une fois par morceau où il apparaissait. Sur une réunion de 29 minutes, cela faisait trois diapositives en double sur neuf. Le résultat dépendait en plus du nombre de morceaux, c’est-à-dire du nombre de cœurs de la machine en face. Nous avons déplacé la vérification sur le thread principal, où elle tourne 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é le moins bon résultat
Que chaque morceau calibre son propre seuil sur sa propre tranche n’a pas de sens : une vidéo, une distribution, un seuil. Nous l’avons donc écrit ainsi. 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 les ratios en commun, la distribution s’élargit, la médiane plus deux MAD monte, et de vraies transitions tombent. Nous avons annulé le changement. Un design peut être plus juste et perdre quand même contre celui que vous avez ; pour le voir, il vous faut quelque chose hors de votre propre code auquel vous comparer.
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, il récupérait tout, et c’est ce qui donnait 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, et ce faux ne veut pas dire que la réponse était non : il veut dire que la requête était encore en route. 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 attendant la réponse, 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 que nous l’avons enregistré, la cause est apparue : 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 réserve 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 cela 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. Quand un nombre avance seul, sans rien à côté, la personne qui regarde en conclut que le programme est bloqué. L’écran indique maintenant quelle moitié du travail tourne et quelle part de la vidéo a été lue, et les mêmes 52 secondes ne font plus l’objet de réclamations.
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 venaient d’un réglage jamais arrivé, d’une réponse pas encore revenue ou d’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 de notre code. Sans elle, nous aurions publié la réécriture du calibrage, parce que c’était le meilleur design, et nous aurions perdu sans le voir trois vraies diapositives sur sept.