Files
abdulive/docs/handoff.md
T
ZeasyAlexandClaude Opus 5 e87879de9e Uebergabe auf den aktuellen Stand
Stand 2026-09-15. Der Discord-Server ist dazugekommen, der Rest ist
unveraendert: S2 wartet weiter auf ein laufendes OBS.

- Neuer Abschnitt "Der Discord-Server steht": was da ist, und dass die drei
  Webhooks fuer Broadcaster, Recap und Clipper bereitliegen. Wer diese
  Werkzeuge baut, muss nur noch posten.
- Neuer Abschnitt "Discord: was gemessen ist", nach dem Vorbild der
  Windows-Fallen. Vier Dinge, die nicht in der Doku stehen, in der man
  zuerst sucht: das Limit von zwei Umbenennungen je zehn Minuten, die
  Rangfolge-Regel hinter dem irrefuehrenden "Missing Permissions", die
  Schwelle fuer Onboarding und die Namensnormalisierung.
- Zwei Handgriffe fuer Abdu ergaenzt: sich selbst die Rolle geben, und den
  Einladungslink verteilen. An Letzterem haengt, ob der Server ueberhaupt
  jemanden bekommt.
- Ehrlich dazugeschrieben, was offen ist: der Knopf haengt am Daemon, und
  der Bot ist nie unter Last gelaufen.
- Der Hinweis auf die zwei Remotes, damit sie nicht auseinanderlaufen.
- 93 -> 162 Tests.

offene-punkte.md bekommt einen Discord-Abschnitt, die Roadmap den Hinweis,
dass die Discord-Seite von M3 schon steht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 22:05:03 +02:00

14 KiB

Übergabe

Stand 2026-09-15, letztes gebautes Paket 0.5.4. Wer hier weitermacht, liest zuerst diese Seite, dann 07-mvp-plan.md. Für Abdu selbst gibt es das Handbuch.

Wo das Projekt steht

Vier von sieben Scheiben des MVP „Clip-Knopf" sind gebaut. Die Kette vom Auslöser bis zum fertigen Short steht im Code vollständig. Geprüft ist sie überall dort, wo es ohne Abdus Stream-PC ging.

Scheibe Stand Nachweis
S0 Toolchain + Auslieferung fertig Paket gebaut und installiert, check-env ohne harte Fehler, Updater einmal komplett durchgelaufen
S1 Einrichtung, Auth, DB, Chat fertig Daemon läuft, Chat verbunden, Secrets verschlüsselt
S2 OBS + !clip Code fertig, nicht abgenommen braucht laufendes OBS
S3 9:16-Render fertig Durchlauf mit künstlicher Aufnahme
S4 Untertitel fertig Durchlauf mit echtem deutschen Clip, Ränder nachgemessen
S5 Onboarding + Sichtung offen
S6 Erstchatter-Begrüßung offen

162 Tests, alle grün. Was noch fehlt, steht vollständig in offene-punkte.md.

Der Discord-Server steht

Dazugekommen seit der letzten Übergabe, und kein Teil des MVP: der Community-Server ist eingerichtet, und zwar als Code statt von Hand. Der Bauplan liegt in packages/discord/src/plan.ts, aufgetragen wird er mit abdulive discord:setup. Wiederholbar: gefunden wird über Namen und frühere Namen, danach wird angeglichen. Der zweite Lauf ändert nichts, gelöscht wird nie.

Das ist Vorarbeit für drei Werkzeuge. Die Webhooks für Go-Live, Recap und Clips liegen fertig im verschlüsselten Speicher unter goLiveWebhook, recapWebhook und clipsWebhook. Wer diese Werkzeuge baut, muss sich um Discord nicht mehr kümmern, sondern nur noch posten.

Sieben Kanäle, vier Rollen, zwei AutoMod-Regeln. Der Entwurf und seine Begründung stehen in projects/discord-community.md; die Kurzfassung ist, dass bei 5 bis 30 Zuschauern fünf sichtbare Kanäle besser sind als zwanzig leere.

Ein Teil davon läuft dauerhaft: der Knopf unter dem Willkommenstext, mit dem man sich die Rolle Live-Ping selbst gibt. Er hängt als Gateway-Verbindung im Ingest-Daemon mit drin und meldet sich als discordConnected auf der Statusseite. Läuft der Daemon nicht, tut der Knopf nichts - der Klick läuft dann in Discords Zeitüberschreitung.

Was jemand wissen muss, bevor er etwas anfasst

Keine Abhängigkeiten, kein Build. Node 24 bringt SQLite, WebSocket, fetch und die Ausführung von TypeScript mit. node apps/cli/main.ts läuft direkt. Deshalb gilt: nur löschbare TypeScript-Syntax, also keine enum, keine Parameter-Properties im Konstruktor, keine namespace. erasableSyntaxOnly in der tsconfig.json erzwingt das und hat schon einen echten Fehler gefangen.

Alles läuft in einem Prozess. Schwere Arbeit (ffmpeg, whisper) läuft als Kindprozess und darf abstürzen. Der Ingest darf nie sterben. Was er nicht mitschreibt, ist für immer weg.

Zugangsdaten kommen aus der Weboberfläche, nicht aus einer Datei. Sie liegen verschlüsselt neben der Datenbank, aber bewusst nicht in ihr. Die Datenbank wird gesichert und weitergereicht, die Geheimnisse nicht.

Das Repo ist seit 2026-09-09 öffentlich. Damit funktioniert der Einzeiler zur Installation, und der Updater braucht keinen Token. Es heißt aber auch: nichts Vertrauliches mehr hineinschreiben, auch nicht in die Doku.

Es gibt zwei Remotes, beide mit demselben Inhalt: origin (ZeasyAlex/abdulive) und discord (ZeasyAlex/abdulive_-discord). Wer pusht, pusht auf beide, sonst laufen sie auseinander.

Die Begründungen stehen in decisions/, acht ADRs. Wer eine davon umwerfen will, liest sie vorher. ADR-0004 (Docker) ist durch ADR-0007 ersetzt; die Analyse darin bleibt lesenswert, weil zwei der vier beschriebenen Fallen mit dem Container weggefallen sind statt gelöst worden zu sein.

Loslegen

Auf Abdus Rechner, eine native Windows-Anwendung:

irm https://git.zeasy.dev/ZeasyAlex/abdulive/raw/branch/main/scripts/install.ps1 | iex

Der Installer lädt das Paket, prüft die Prüfsumme, holt das Sprachmodell, läuft den Selbsttest, startet den Dienst und öffnet die Einrichtungsseite. Ohne Administratorrechte. Ausführlich in 05-setup.md.

Zum Entwickeln reicht ein Checkout und Node 24 oder neuer:

node apps/cli/main.ts check-env
node --test "packages/**/*.test.ts" "apps/**/*.test.ts"
node scripts/build-package.ts --version 0.5.5     # baut dist/

Typprüfung, das Einzige wofür npm gebraucht wird:

npm i typescript@5.9 @types/node
npx tsc --noEmit

Ausliefern

build-package.ts lädt Node, ffmpeg und whisper.cpp bei ihren Herausgebern, packt alles nach dist/ und schreibt abdulive.json. Dieses Manifest fragt abdulive update auf Abdus Rechner ab.

Die Veröffentlichung latest trägt beide Dateien und wird bei jedem Bau ersetzt, damit die Adresse gleich bleibt. .gitea/workflows/release.yml macht das beim Tag automatisch. Die Pipeline ist noch nie gelaufen, bisher wurde von Hand veröffentlicht. Der Runner (ubuntu-latest) steht.

Windows-Fallen, die schon zugeschlagen haben

Alle vier haben Zeit gekostet, und keine sieht im Fehlerfall nach ihrer Ursache aus. Wer an den PowerShell-Skripten arbeitet, sollte sie kennen.

stderr beendet das Skript. Windows PowerShell 5.1 verpackt jede stderr-Zeile eines nativen Programms in einen ErrorRecord. Zusammen mit $ErrorActionPreference = 'Stop' bricht das sofort alles ab. Der Daemon schreibt im Normalbetrieb Warnungen auf stderr, der Starter hat ihn also bei seiner ersten Logzeile umgebracht: Aufgabe meldete Code 1, das Protokoll war leer. Deshalb läuft der Start über Start-Process mit getrennten Umleitungen, und install.ps1 ruft native Programme über eine Hilfsfunktion auf, die ErrorActionPreference kurz zurückstellt.

& $programm; return $LASTEXITCODE gibt auch die Ausgabe zurück. Die Funktion liefert dann ein Array aus allen Ausgabezeilen plus Exit-Code, und jeder Vergleich damit ist falsch, ohne dass es auffällt. Ein erfolgreiches fetch-model galt so als fehlgeschlagen. Ausgabe mit | Out-Host durchreichen.

localhost kostet zwei Sekunden. Der Dienst lauscht nur auf IPv4, Windows probiert bei localhost zuerst ::1. Gemessen: 2066 ms bis zum Rückfall gegenüber 15 ms bei 127.0.0.1. Eine Bereitschaftsprüfung mit zwei Sekunden Zeitlimit läuft dadurch immer ab, obwohl der Dienst längst läuft.

Windows-Pfade im ffmpeg-Filtergraphen brauchen drei Schritte. An echtem ffmpeg 8.1 durchprobiert:

Form Ergebnis
ass=filename=C:/x/s.ass No option name near '/x/s.ass'
ass=filename=C\:/x/s.ass ebenso
ass=filename='C:/x/s.ass' ebenso
ass=filename='C\:/x/s.ass' läuft

Vorwärtsschrägstriche, escapter Doppelpunkt und Anführungszeichen. Je zwei davon reichen nicht. filterPath() in packages/media/src/ffmpeg.ts macht das, mit Tests.

Untertitel: was gemessen ist

Die Schrift muss mitgeliefert werden. Im Container kam DejaVu Sans aus dem Betriebssystem, Windows hat sie nicht. Fehlt fontsdir, setzt libass wortlos eine Ersatzschrift ein. Gemessen: dieselbe Zeile wird 720 statt 826 Pixel breit, ohne jede Fehlermeldung. Die Dateien liegen unter assets/fonts/.

Die Zeilenlänge ist eingemessen, nicht geschätzt. Vorher stand dort fest maxChars: 30. Das sind bei Schriftgröße 76 rund 1085 px, nutzbar sind 940. Zusammen mit WrapStyle: 2, bei dem libass gar nicht umbricht, lief der Text links und rechts aus dem Bild. Jetzt wird die Grenze aus Breite und Schriftgröße abgeleitet, Versalien zählen breiter, und einzelne zu lange Wörter bekommen eine kleinere Schrift, weil libass innerhalb eines Wortes nicht umbricht. Deutsche Komposita sind regelmäßig breiter als das Bild.

Wer die Schrift wechselt, muss CHAR_WIDTH_RATIO in subtitles.ts neu einmessen. Sonst bricht die Zeile wieder an der falschen Stelle.

Discord: was gemessen ist

Vier Dinge, die nicht in der Dokumentation standen, in der man zuerst sucht. Alle am echten Server nachgeprüft.

Kanalnamen lassen sich nur zweimal in zehn Minuten ändern. Danach verlangt Discord eine Pause, gemessen wurden 529 Sekunden. Ein Einrichtungslauf, der brav auf ein 429 wartet, steht dann neun Minuten. Der Client wartet deshalb höchstens eine Minute, meldet den Kanal als übersprungen und macht weiter.

Eine Rolle kann nur Rollen unterhalb der eigenen verschieben - auch mit Administrator. Discord hängt die Rolle eines frisch eingeladenen Bots ganz unten ein, also gibt es dort keinen Platz. Der erste Lauf quittierte das mit Missing Permissions, was nach einem fehlenden Recht aussieht und keines ist. rolePositions() rechnet den Platz jetzt aus und sagt, was zu tun ist.

Discords Onboarding braucht einen größeren Server.

HTTP 400  Cannot enable onboarding, requirements are not met  (350000)

sichtbar für @everyone: 5   davon beschreibbar: 3
Discord verlangt:       7   davon beschreibbar: 5

Zwei Kanäle anzulegen, damit eine Schwelle passt, hätte den Server leerer gemacht statt besser. Daher der Knopf.

Kanalnamen werden normalisiert, aber nicht so weit wie erwartet. Textkanäle werden kleingeschrieben und Leerzeichen zu Bindestrichen; Emoji, Umlaute und das Trennzeichen │ (U+2502) bleiben unverändert stehen. normalizeName() bildet das ab. Ohne diesen Abgleich geht bei jedem Lauf dieselbe Umbenennung erneut raus - und läuft nach zwei Läufen in das Limit von oben.

Die drei Dinge, die Abdu tun muss

  1. Broadcaster-Konto verbinden. Die beiden Twitch-Apps (dev.twitch.tv/console/apps, Redirect-URI http://localhost:4792/callback) kann auch jemand anders anlegen, sie hängen nicht am Kanal. Was nur Abdu kann: auf der Einrichtungsseite eingeloggt als er selbst auf „Mit Twitch verbinden" klicken. Dafür muss die Anwendung auf seinem Rechner laufen, die Seite hört nur auf 127.0.0.1.
  2. OBS einrichten: obs-websocket an (Port 4455), Wiedergabepuffer 90 s, Hotkey auf „Wiedergabepuffer speichern", getrennte Audiospuren.
  3. Layout umstellen nach obs-layout.md, danach abdulive calibrate zur Kontrolle.

Ohne Punkt 1 liest das Toolkit den Chat anonym mit und kann nichts senden. Ohne Punkt 2 gibt es keine Clips. Ohne Punkt 3 sitzt der Ausschnitt schief.

Dazu eine Kleinigkeit mit Wirkung: das Bot-Konto sollte Moderator sein (/mod <botname>). Ohne das gilt ein Limit von 20 Chat-Nachrichten je 30 Sekunden statt 100, und im Follower-only-Modus kommt der Bot gar nicht durch.

Und zwei für Discord, beide nicht über die API machbar:

  1. Sich selbst die Rolle Abdu geben. Das Skript legt Rollen an, aber es hängt sie niemandem um.
  2. Einladungslink ohne Ablaufdatum erzeugen und ins Twitch-Panel, in die Kanalinfo und in einen Chat-Befehl eintragen. Ein Server, den im Stream niemand erwähnt, bleibt leer. Das ist der eine Handgriff, an dem hängt, ob der Server überhaupt jemanden bekommt.

Wo ich weitermachen würde

S2 an einem laufenden OBS abnehmen. Das ist die einzige ungeprüfte Stelle in der ganzen Kette, und alles Weitere hängt daran. Seit es abdulive sources gibt, ist auch die Frage beantwortbar, welche OBS-Quelle fremden Text ins Bild malt. Das war vorher nicht zu erraten.

Discord braucht jetzt nichts mehr. Der Server steht, die Webhooks liegen bereit. Wer als Nächstes apps/broadcaster oder apps/recap baut, findet alles vor und muss nur noch posten. Das wäre auch ein guter Nebenweg, wenn der Stream-PC gerade nicht verfügbar ist, denn anders als S2 hängt daran nichts.

Danach S5. Nicht wegen des Codes, sondern weil das Werkzeug ohne Sichtungsseite für Abdu nicht bedienbar ist. Die Kette funktioniert, aber sie hat keine Oberfläche, auf der man Clips durchgeht und aussortiert. Abnahmekriterium steht im Plan: Ein Fremder kommt von „nichts eingerichtet" bis zum ersten eigenen Clip, ohne die Dokumentation zu lesen. Installer und Handbuch decken davon inzwischen ein gutes Stück ab.

Womit ich nicht zufrieden bin

Das Aufräumen im Arbeitsordner fehlt komplett. Rohdateien sammeln sich unbegrenzt an, mehrere hundert MB je Clip. Das ist die Lücke, die als Erstes weh tun wird.

Die Untertitel sind brauchbar, nicht gut. ggml-small verhört sich hörbar („Danke" wurde „Dank es"), DejaVu Sans ist unauffällig, und das fortlaufende Einfärben ist nicht der Stil, der auf Shorts trägt. Sechs konkrete Verbesserungen stehen in offene-punkte.md.

Die CI ist geschrieben, aber ungetestet. Beim ersten Tag zusehen.

Das Paket ist nicht signiert. SmartScreen warnt bei jeder Installation. Solange nur Abdu installiert, reicht ein Klick. Sobald jemand Drittes es bekommt, nicht mehr.

Der Discord-Knopf hängt am Daemon. Läuft der Ingest nicht, passiert beim Klick nichts, und die klickende Person sieht nur „Interaktion fehlgeschlagen". Das ist vertretbar, solange der Daemon ohnehin durchläuft, aber es ist eine stille Abhängigkeit zwischen zwei Dingen, die sonst nichts miteinander zu tun haben.

Der Discord-Bot ist nie unter Last gelaufen. Getestet sind die Bausteine und ein Klickweg gegen einen nachgebauten Server; am echten Server ist die Verbindung nachgewiesen und der Rollenwechsel einmal in beide Richtungen durchgespielt. Was bei gleichzeitigen Klicks oder nach Tagen Laufzeit passiert, ist offen.

Es gibt keine Einstellung für den OBS-Wiedergabepuffer-Ordner. Im Container kam der Pfad über OBS_REPLAY_DIR, nativ fehlt der Weg. Für die Clip-Kette ist das egal, weil OBS den Pfad über WebSocket meldet. Aber calibrate findet so keine Aufnahmen, und check-env warnt zu Recht.