Zum Inhalt springen
Video2Any
2026-08-23

Video parallel dekodieren, in einem Tab

Video2Any hat eine Einschränkung, aus der alles andere folgt: Dein Video verlässt deinen Rechner nicht. Kein Upload, also kein Server, der die Arbeit macht, also passieren Dekodieren, Vergleichen und Rendern alle in einem Browser-Tab, der nebenbei bedienbar bleiben muss.

Was das tatsächlich kostet, und worauf der Rest der Seite gebaut ist.

Den Demuxer schreibst du selbst

WebCodecs gibt dir einen VideoDecoder, der kodierte Chunks in Frames verwandelt. Die Chunks gibt er dir nicht. Der Browser hat einen MP4-Parser — so funktioniert <video> —, und keine API reicht ihn dir weiter. Also liest du den Container selbst: Boxen durchgehen, moov finden, die Sample-Tabelle lesen und dem Decoder einen EncodedVideoChunk nach dem anderen geben.

Das hat einen angenehmen Nebeneffekt. Wenn du den Container ohnehin liest, kannst du jemandem sagen, warum seine Datei nicht aufgehen wird, bevor fünf Minuten mit Scheitern vergehen: ProRes ist ein Schnittformat, das kein Browser dekodiert, und man erfährt das beim Lesen des moov — das bei ProRes oft am Dateiende steht statt am Anfang. Genau deshalb sieht unsere Probe an beiden Enden nach.

Zwei Durchläufe, wegen des Speichers

Naheliegend wäre, jeden Frame zu dekodieren und zu vergleichen. Eine 92-Minuten-Aufzeichnung mit 25 fps sind 138.000 Frames; in 1080p hält das kein Tab.

Also zwei Durchläufe. Der Sweep dekodiert nur die Abtastpunkte und behält jeden in 160×90: klein genug, dass ein ganzes Video ein paar Dutzend Megabyte Pixel sind, groß genug, um eine Folie von der nächsten zu unterscheiden. Erst wenn der Detektor gewählt hat, holt der Capture-Durchlauf die Frames in voller Auflösung — und nur die gewählten. Eine Grenze von 900 Samples deckelt den ersten Durchlauf, mit einer schrägen Folge: ein 29-Minuten-Video und ein 92-Minuten-Video kosten ungefähr gleich viel, weil die Kosten pro Sample anfallen, nicht pro Minute.

Ein Worker je Abschnitt, jeder mit eigenem Decoder

Das Video wird zeitlich geteilt, und jeder Abschnitt bekommt einen Worker mit eigenem VideoDecoder. Der Pool ist min(4, Kerne / 2): die Hälfte der Kerne, weil der Tab noch eine Oberfläche bedienen muss, und gedeckelt bei vier aus dem Grund im nächsten Abschnitt.

Jeder Abschnitt beginnt etwas vor seinem eigenen Bereich zu tasten, damit der Detektor beim ersten eigenen Frame etwas zum Vergleichen hat, und verwirft danach alles außerhalb. Folien kommen heraus, sobald ihre Aufnahme fertig ist, nicht erst am Ende.

Was das Aufteilen kostet

Zwei Dinge, und da wird es interessant. Jeder Abschnitt kalibriert seinen Schwellenwert auf seinem eigenen Ausschnitt, also ergibt dasselbe Video in vier und in acht Teilen unterschiedliche Foliensätze — das Ergebnis hängt davon ab, wie viele Kerne der Rechner hat. Und jeder Abschnitt entfernte Duplikate nur in sich selbst, also überlebte eine Einstellung, auf die eine Besprechung immer wieder zurückschneidet, einmal pro Abschnitt: drei doppelte Folien von neun bei einer 29-minütigen Aufzeichnung.

Die Duplikate sind erledigt: Die Prüfung liegt im Haupt-Thread und läuft, sobald eine Folie eintrifft. Die Kalibrierung nicht. Wir haben die globale Variante gebaut, und sie machte das Ergebnis schlechter: bei derselben Besprechung, in der ffmpeg sieben echte Schnitte zählt, liefert Kalibrierung pro Abschnitt sechs und global vier. Der Pool bleibt also bei vier, denn mehr Abschnitte sind etwa 25 % schneller und ändern trotzdem die Antwort.

Vor dem Ende des Videos lässt sich nichts zeigen

Der Schwellenwert kommt aus der Verteilung der Frame-Differenzen über das ganze Video, die erste Folie lässt sich also nicht wählen, bevor der letzte Frame abgetastet ist. Bei einer 92-Minuten-Aufzeichnung dauert der Sweep 52 Sekunden, und die erste Folie erscheint in Sekunde 50.

Das ist kein Performance-Problem, und keine Parallelität der Welt behebt es. Es ist ein Rückmeldungsproblem, und gelöst wurde es damit, dort „Video wird gelesen · 45:46 von 92:08“ hinzuschreiben, wo vorher nur eine Prozentzahl stand.

Der Rest des Stacks

Cloudflare Workers mit Hono für die API, D1 für Konten und Metadaten, R2 für die wenigen Dinge, die gespeichert werden. React 19 mit TanStack Router, Tailwind v4, better-auth. Acht Sprachen, jede mit eigenem URL-Präfix und eigener Router-Basepath.

Die Seite ist eine vorgerenderte SPA statt serverseitig gerendert: Jede öffentliche Seite ist ein statischer HTML-Schnappschuss aus dem Deploy, deshalb sind die Marketingseiten cachebar und schnell, und der Konverter kommt ganz ohne Server aus. Genau an einer Stelle brechen wir das. Die /app-Seiten sind statische Hüllen, der Editor startete also ohne jedes Wissen über dein Konto und musste fragen — und mit der Arbeit zu beginnen, bevor die Antwort da war, ist genau der Weg, auf dem ein zahlender Abonnent die Dreißig-Minuten-Grenze der Gratis-Stufe abbekam. Der Worker hat die Session ohnehin, wenn er diese Seiten ausliefert, also schreibt er den Plan ins Dokument, und das erste Rendern weiß Bescheid. Diese Antworten sind privat und werden nie gecacht; alle anderen Seiten behalten ihr öffentliches Caching.

Warum der Aufwand

Ein Server ließe das meiste davon verschwinden. Er hieße aber auch, dass jede Vorlesung, jede interne Besprechung und jede medizinische Aufzeichnung, die jemand umwandelt, über unsere Rechner läuft — und der ganze Sinn des Produkts ist, dass sie das nicht tun.

Die Einschränkung ist das Feature. Alles oben ist ihr Preis.

Video umwandelnBlog