Acht Bugs, die nach dem Algorithmus aussahen
Video2Any findet die Folien in einer Aufzeichnung, indem es im Browser Frames dekodiert, jeden mit dem vorherigen vergleicht und die behält, die sich genug verändert haben. Wenn das Ergebnis nicht stimmt, ist dieser Vergleich der naheliegende Verdächtige. Er ist es fast nie.
Acht Dinge, die wir in einer Woche gejagt haben. Zwei lagen am Detektor. Der Rest: ein stehengebliebener Guard, ein lügendes Testskript, eine virtualisierte Liste, ein Wettlauf mit einem API-Aufruf, eine Speichergrenze, und ein Fehler-Log, das den einzigen brauchbaren Teil von sich selbst wegwarf.
Die acht
1. Das Bedienelement, das nichts tat
Gemeldet wurde, dass ein kurzes Video auf Dicht drei Folien liefert, genau wie auf Standard. Wir haben eine Weile auf die Schwellenwert-Formel gestarrt, bevor wir das Naheliegendste geprüft haben: Kam die Einstellung überhaupt an?
Kam sie nicht. Der erste Durchlauf ist per Ref abgesichert, damit er pro Datei genau einmal läuft. Die Dichte zu ändern baute den Callback neu, aber der Guard schickte den Effekt sofort wieder zurück, und das Einzige, was tatsächlich neu lief, war ein Button in einem völlig anderen Hinweis. Nach dem Umschalten sah man das vorherige Ergebnis, mit denselben Zeitstempeln. Auf diesem Clip liefern Sparsam / Standard / Dicht jetzt 3, 6 und 13. Vorher lieferten alle drei 6.
2. Der Prüfstand hat zweimal gelogen
Um ein merkwürdiges Ergebnis zu erklären, haben wir die Pipeline offline in Node nachgebaut, die echte Datei hineingegeben, und die Zahlen wichen von der App ab — beide Male in die entgegengesetzte Richtung: erst 1 Folie im Skript gegen 6 in der App, nach der Korrektur 15 gegen 6.
Zwei Ursachen. Das Skript zog die Frames mit ffmpeg in Graustufen, während der Browser der Engine RGBA aus einem Canvas übergibt, und der Unterschied zwischen beiden Skalierungen reicht, um den kalibrierten Schwellenwert von 0,618 auf 0,650 zu verschieben. Außerdem übersprang es die Funktion, die über die Aktivitätsmaske entscheidet, lief also gar nicht den Weg, den die App läuft. Ein Prüfstand, der den Algorithmus nachbaut, aber nicht die Pipeline, liefert eine falsche Antwort mit voller Überzeugung.
3. Im DOM stand nur, was auf dem Bildschirm war
Ein Test prüfte, ob ein 32-Minuten-Video Folien jenseits der 30-Minuten-Marke erzeugt. Er las den Zeitstempel jedes Thumbnails, und der letzte lag bei 27:00 — genau so sieht eine abgeschnittene Analyse aus.
Das Raster ist virtualisiert: Nur die sichtbaren Zeilen existieren als Elemente. Erst ganz nach unten scrollen, dann lesen, und die wirklich letzte lag bei 32:00. Die Assertion hatte die ganze Zeit den Viewport gemessen.
4. Jeder Worker entfernte Duplikate für sich
Lange Videos werden in parallelen Abschnitten abgetastet. Jeder Worker verwirft wiederholte Einstellungen innerhalb seines eigenen Abschnitts — jemand kehrt zu einer früheren Folie zurück, eine Konferenz schneidet auf dieselbe Ansicht zurück — und keiner sieht die anderen.
Eine wiederkehrende Einstellung überlebte also einmal pro Abschnitt, in dem sie vorkam. Bei einer 29-minütigen Besprechung waren das drei doppelte Folien von neun. Und es machte das Ergebnis davon abhängig, wie viele Abschnitte es gab, also davon, wie viele Kerne der Rechner am anderen Ende hat. Die Prüfung liegt jetzt im Haupt-Thread und läuft, sobald eine Folie eintrifft — ein Duplikat erscheint also gar nicht erst, um dann wieder zu verschwinden.
5. Der bessere Entwurf lieferte das schlechtere Ergebnis
Dass jeder Abschnitt seinen eigenen Schwellenwert auf seinem eigenen Ausschnitt kalibriert, ist offensichtlich schief: ein Video, eine Verteilung, ein Schwellenwert. Also haben wir genau das gebaut. Die Worker schicken die gemessenen Verhältnisse, der Haupt-Thread führt sie zusammen, kalibriert einmal und schickt die Antwort zurück.
Der Abstand zwischen vier und acht Abschnitten schrumpfte von drei Folien auf eine. Und das Ergebnis wurde schlechter. Bei derselben 29-Minuten-Besprechung, in der ffmpeg mit eigener Szenenerkennung sieben echte Schnitte zählt, liefert Kalibrierung pro Abschnitt plus die obige Duplikatentfernung sechs, globale Kalibrierung vier. Das Zusammenführen verbreitert die Verteilung, hebt Median plus zwei MAD, und echte Wechsel fallen heraus. Wir haben es zurückgenommen. Ein Entwurf kann sauberer sein und trotzdem gegen den verlieren, den man hat — und wissen kann man das nur, wenn man etwas außerhalb des eigenen Codes zum Abgleichen hat.
6. Falsch, weil wir noch nicht gefragt hatten
Ein zahlender Abonnent lud eine lange Aufzeichnung hoch und bekam nur die ersten dreißig Minuten. Ein Klick auf Neu abtasten lieferte alles — und genau deshalb sah es nach einem Erkennungsproblem aus.
Der Editor beginnt, bevor er weiß, wer da ist. Das Abo-Flag startet auf falsch, nicht weil die Antwort nein war, sondern weil die Anfrage noch unterwegs war, und die Längenbegrenzung las dieses falsch und legte die Obergrenze der Gratis-Stufe an. Als das neue Abtasten lief, war der Plan längst da. Zuerst haben wir darauf gewartet, dann das Warten überflüssig gemacht: Der Worker hat die Session ohnehin, wenn er die Seite ausliefert, also schreibt er den Plan ins Dokument und das erste Rendern weiß Bescheid.
7. Jeder Fehler hieß „Error“
Die Transkription scheiterte häufiger, als sie gelang, sechs zu fünf, und jeder Fehlschlag wurde als die Zeichenkette „Error“ protokolliert. Die neunzehn gescheiterten Extraktionen ebenso.
Der Code speicherte err.name, und das ist bei jedem von Hand erzeugten Error „Error“. Die Message, in der die Information immer schon steckte, landete nirgends. Nachdem wir sie protokolliert hatten, bekam der Fehlschlag einen Mechanismus: decodeAudioData resampelt auf die Rate des Audio-Kontexts, also entstehen beim Dekodieren einer 55-minütigen Vorlesung mit den 48 kHz Stereo der Hardware zuerst rund 630 MB Float-Samples, bei zwei Stunden 1,4 GB. Dekodiert man auf einem Kontext, der ohnehin bei 16 kHz liegt, bekommt das Modell dasselbe Audio bei etwa einem Sechstel des Speichers.
8. Es war schnell und fühlte sich trotzdem kaputt an
Eine 92-minütige Besprechungsaufzeichnung ist in 52 Sekunden durchgetastet. Die erste Folie erscheint in Sekunde 50.
Nichts daran war langsam. Der Schwellenwert wird aus der gesamten Verteilung der Frame-Differenzen kalibriert, es kann also keine Folie gewählt werden, bevor das Video von vorn bis hinten gelesen ist — und in diesen 50 Sekunden stand nur eine Prozentzahl auf dem Bildschirm. Eine Prozentzahl, die ohne alles andere vor sich hin läuft, liest sich wie Stillstand. Jetzt steht dort, welche Hälfte der Arbeit läuft und wie viel des Videos gelesen ist, und dieselben 52 Sekunden sind keine Beschwerde mehr.
Was wir uns von vor einer Woche sagen würden
Prüfe, ob die Eingabe überhaupt im Code angekommen ist, bevor du über den Code nachdenkst. Die Hälfte davon waren Einstellungen, die nie ankamen, Antworten, die noch nicht zurück waren, oder ein Test, der auf etwas anderes schaute.
Und halte dir etwas außerhalb der eigenen Pipeline zum Abgleichen. Die Szenenerkennung von ffmpeg ist nicht das, was wir bauen, und muss es auch nicht sein: Sie muss nur unabhängig sein. Ohne sie hätten wir die neu geschriebene Kalibrierung veröffentlicht, weil sie der bessere Entwurf war — und dabei still drei von sieben echten Folien verloren.