Einblendungen, Chat, Statusleiste, Effekte, Pausenbild. Kein Build, kein
Framework - OBS laedt die Dateien direkt, und jede Zwischenstufe waere ein
Ding, das mitten im Stream kaputtgehen kann.
Server-Sent Events statt WebSocket, entgegen der Skizze in chat-control.md:
Node bringt einen WebSocket-*Client* mit, aber keinen Server, `EventSource`
verbindet sich von allein neu, und die Richtung stimmt - das Overlay hoert
nur zu. Gegen die tote Browser-Source nach einem Neustart stehen drei Dinge:
der eingebaute Reconnect, ein Herzschlag als echtes Ereignis (ein
SSE-Kommentar haelt die Verbindung offen, ist im Browser aber unsichtbar),
und ein Zustands-Schnappschuss beim Verbinden.
Abos, Raids, Bits und Loeschungen kommen ueber IRC-USERNOTICE und CLEARCHAT
herein; das wurde bisher weggeworfen. Follows fehlen weiter, die gibt es nur
ueber EventSub.
GIFs im Chat: Twitch schickt dafuer einen Tag `gifs`, den wir nicht kannten.
gifs=0-22|Pnd7qLFuGCOntZcNKB|https://media0.giphy.com/media/.../giphy.gif
PRIVMSG #abdulive_ :[Idk GIF by Respective]
Der Nachrichtentext ist nur ein Platzhalter, der Bereich markiert ihn.
Ausgewertet wird er jetzt; ohne das stand woertlich "[Idk GIF by Respective]"
im Bild. Gefunden wurde der Tag durch einen Rohmitschnitt - in 3094
Nachrichten aus acht grossen Kanaelen war kein einziges GIF, weil die Adresse
eben nicht im Text steht.
Zwei Fehler, die beim Durchklicken im Browser aufgefallen sind:
- Die Aufraeumfunktion im Chat zaehlte Zeilen, die erst nach der
Ausblend-Animation aus dem DOM verschwinden, und lief in eine
Endlosschleife. In OBS waere die Quelle beim vierzehnten Chatbeitrag
eingefroren.
- Der Bereichsparser der GIFs war zu nachsichtig: `-1-4` zerfaellt beim
Trennen in ['', '1', '4'], und `Number('')` ist 0 - der Bereich saehe
gueltig aus und wuerde den falschen Textteil ersetzen.
Auf der Uebersichtsseite steht fuer jede Einblendung, jede Chat-Variante und
jeden Effekt ein Knopf. Ohne den liesse sich eine Raid-Einblendung nur
pruefen, indem man auf einen echten Raid wartet. Was einen Zustand setzt, ist
waehrend eines Streams gesperrt; "Stand zuruecksetzen" ausdruecklich nicht,
sonst kaeme man aus einem erfundenen Zustand nicht mehr heraus.
build-package.ts nimmt jetzt auch html/css/js mit - sonst liefert das Paket
404 statt Overlay.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei Dinge, die Reichweite bringen sollen, hingen bisher daran, dass jemand
vor seinem Rechner sitzt: der Discord-Rollen-Knopf und die Go-Live-
Ankuendigung. Beide brauchen nur Internet.
Der Schnitt verlaeuft entlang einer Frage: braucht die Aufgabe eine Datei
oder ein Programm, das nur auf dem Stream-PC existiert?
Hub (LXC) Ingest (Abdus PC)
--------- -----------------
Chat, Zuschauerzahlen OBS-Wiedergabepuffer
Discord: Knopf und Go-Live !clip, Rendern, Untertitel
Overlay-Seiten fuer OBS
Beide schreiben in ihre eigene Datenbank. Eine gemeinsame waere eine
Netzwerkfreigabe fuer SQLite - der zuverlaessigste bekannte Weg zu einer
beschaedigten Datei.
Das kostet ADR-0005 seine Hauptbegruendung: "bindet nur auf 127.0.0.1" gilt
fuer den Hub nicht mehr. An die Stelle treten ein Passwort mit signiertem
Sitzungs-Cookie fuer alles, was ein Mensch bedient, und ein zufaelliger
Schluessel in der Adresse fuer die Browser-Sources. Der Schluessel ist kein
schoenes Verfahren - er steht in jedem OBS-Profil -, aber eine Browser-Source
kann kein Formular ausfuellen und keinen Login durchhalten.
`publicUrl` haengt der OAuth-Rueckruf daran. Twitch vergleicht die
Redirect-URI zeichengenau, und ein fehlender Schraegstrich sieht im Fehlertext
aus wie ein falsches Client-Secret.
Die Routen der Einrichtungsseite sind aus ingest/server.ts herausgeloest,
weil sie jetzt an zwei Servern haengen.
Begruendung und Konsequenzen in ADR-0008.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Bauplan verspricht, dass ein zweiter Lauf nichts aendert. Zwei Schritte
haben das gebrochen, weil sie ohne Vergleich geschrieben haben: die
Servereinstellungen und die Rangfolge der Rollen.
Aufgefallen beim Einrichten des Technik-Webhooks - der Lauf meldete "GESETZT
abdulive_" und "GESETZT Rangfolge", obwohl die Vorschau zuvor "steht" gesagt
hatte. Vorschau und echter Lauf sagten also Verschiedenes.
Folgenlos war es nicht: jeder Lauf kostete zwei Aufrufe und hinterliess zwei
Eintraege im Audit-Log des Servers, einen davon eine Rollen-Umsortierung.
Jetzt wird beides vorher verglichen. ApiGuild traegt dafuer die vier Felder
aus GUILD_SETTINGS. Ein Test haelt es fest: nach einem Lauf darf der zweite
weder PATCH /guilds/{id} noch PATCH /guilds/{id}/roles schicken.
Am echten Server nachgeprueft - der dritte Lauf meldet keine Aenderung mehr.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der billigste Hebel im Repo, und bis jetzt nur Vorarbeit: die Webhooks lagen
seit der Discord-Einrichtung bereit, gepostet hat nie jemand.
Ablauf: neuer Stream erkannt, eine Minute warten (Twitch setzt Titel und
Kategorie erst kurz nach dem Start, wer sofort postet kuendigt den Titel des
letzten Streams an), Daten frisch holen, Webhook posten, Rolle Live-Ping
anpingen.
Der Ping funktioniert nur mit `allowed_mentions` im Nachrichtenkoerper. `<@&ID>`
allein faerbt die Erwaehnung lila ein und benachrichtigt niemanden - der
Fehler, den man erst nach drei leeren Streams bemerkt. Dagegen steht ein
eigener Test.
Gegen den doppelten Post stehen drei Dinge, denn wer zweimal in zehn Minuten
angepingt wird, nimmt sich die Rolle wieder ab und ist damit fuer immer stumm:
- Tabelle `posts` mit eindeutigem Index je Stream und Plattform. Nur
*gesendete* Posts sperren; ein fehlgeschlagener Versuch darf wiederholt
werden, sonst kostet ein Netzwerkaussetzer die ganze Ankuendigung.
- Abklingzeit von sechs Stunden, gegen die neue Stream-ID nach einem
Verbindungsabbruch.
- Ausgeloest wird nur beim erkannten Neustart, nie im Fortsetzungszweig.
`stream_id` ist bewusst kein Fremdschluessel: ein Post ist die Quittung fuer
etwas, das den Rechner verlassen hat, und muss auch dann schreibbar sein,
wenn es die Sitzung nicht gibt - beim Probelauf etwa.
Dazu ein vierter Webhook in #technik-log. Der Entwurfsmodus postet dieselbe
Nachricht dorthin, ohne Ping und nur fuer Mods sichtbar. So sieht man das
Ergebnis, bevor es der Server sieht.
Die Rollenaufloesung aus selfroles.ts liegt jetzt in roles.ts, weil der Ping
sie auch braucht: ein Webhook kann eine Rolle nicht ueber ihren Namen
anpingen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Live-Ping nuetzt nur, wenn man sie sich selbst geben kann. Discords
Onboarding waere der vorgesehene Weg, laesst sich hier aber nicht
einschalten - gemessen, nicht vermutet:
HTTP 400 Cannot enable onboarding, requirements are not met (350000)
sichtbar fuer @everyone: 5 davon beschreibbar: 3
Discord verlangt: 7 davon beschreibbar: 5
Zwei Kanaele anzulegen, damit eine Schwelle passt, macht den Server leerer
statt besser. Stattdessen ein Knopf unter dem Willkommenstext: ein Druck
holt die Rolle, der naechste gibt sie ab, die Antwort sieht nur, wer
geklickt hat.
Der Knopf braucht eine dauerhafte Verbindung, weil Discord Klicks ans
Gateway oder an eine oeffentliche HTTPS-Adresse liefert und der Stream-PC
keine hat:
- gateway.ts WebSocket mit Herzschlag, Fortsetzen und Wiederverbinden.
Bei 4004 und Verwandten wird aufgegeben statt im Kreis zu
laufen - dagegen hilft nur eine Aenderung an der App.
intents: 0, der Bot sieht weder Nachrichten noch Mitglieder.
- selfroles.ts Knopfleiste und Rollenwechsel.
- bot.ts beides zusammen, laeuft im Ingest-Daemon mit. Ohne
hinterlegtes Token entfaellt er wortlos.
Neuer Befehl `abdulive discord:bot` zum Ausprobieren; im Normalbetrieb
haengt der Bot im Daemon und meldet sich als discordConnected auf der
Statusseite.
Die Knopfleiste gehoert zum Soll-Zustand des Willkommenstextes: fehlt sie,
wird sie nachgereicht, ohne die Nachricht neu zu posten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Server ist kein Tool aus der Neunerliste, aber Broadcaster, Recap und
Clipper posten dorthin. Deshalb steht die Struktur als Code im Repo und
nicht nur in einer Oberflaeche.
packages/discord:
- permissions.ts Rechtebits als BigInt, das Feld ist 64 Bit breit
- api.ts REST-Client: Warteschlange, Rate Limits, lesbare Fehler
- plan.ts Rollen, Kanaele, Rechte, AutoMod, Willkommenstext
- apply.ts traegt den Bauplan auf einen bestehenden Server auf
Neuer Befehl `abdulive discord:setup [--server ID] [--vorschau]`.
Zugeschnitten auf 5 bis 30 gleichzeitige Zuschauer: fuenf sichtbare Kanaele
statt zwanzig, und eine selbst vergebene Rolle Live-Ping statt eines
Rundrufs an alle bei jedem Stream.
Wiederholbar statt einmalig: gefunden wird ueber Namen und frueheren Namen,
danach wird angeglichen. Der zweite Lauf aendert nichts. Geloescht wird nie,
und von Hand gesetzte Rechte-Ausnahmen bleiben stehen.
Drei Dinge, die am echten Server nachgemessen und nicht angenommen wurden:
- Discord schreibt Textkanaele klein und macht aus Leerzeichen Bindestriche;
Emoji, Umlaute und U+2502 bleiben. normalizeName() bildet das ab, sonst
geht bei jedem Lauf dieselbe Umbenennung erneut raus.
- Kanalnamen lassen sich nur zweimal je zehn Minuten aendern, danach
verlangt Discord bis zu neun Minuten Pause. Der Lauf wartet hoechstens
eine Minute und meldet den Rest.
- Eine Rolle kann nur Rollen unterhalb der eigenen verschieben, auch mit
Administrator. rolePositions() rechnet den Platz unter der Bot-Rolle aus,
statt blind 3, 2, 1 zu verlangen.
Zugangsdaten laufen ueber den verschluesselten Speicher (ADR-0005): der
Bot-Token wird verdeckt abgefragt, die drei Webhook-URLs werden nie
ausgegeben und nie geloggt.
Im Quelltext bleibt es bei ae/oe/ue, alles was ein Mitglied auf Discord
liest steht in richtigem Deutsch. Ein Test haelt das fest.
Begruendung, Fallstricke und die Schritte von Hand:
docs/projects/discord-community.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Version 0.5.4, 93 Tests, acht ADRs. Neu darin:
- Auslieferung: was build-package.ts tut, wie die Veroeffentlichung
entsteht, und dass die CI-Pipeline noch nie gelaufen ist
- Vier Windows-Fallen, die je einen Fehler verursacht haben und im
Fehlerfall nicht nach ihrer Ursache aussehen: stderr als
NativeCommandError, Rueckgabewert von & plus $LASTEXITCODE, localhost
gegen 127.0.0.1, ffmpeg-Filterpfade
- Untertitel: die gemessenen Zahlen statt der alten Schaetzung
- Hinweis, dass das Repo oeffentlich ist
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
docs/handbuch/handbuch.tex plus bauen.ps1. Acht Seiten: was das Werkzeug
tut, Installation, Einrichtung Schritt fuer Schritt, OBS, Betrieb,
Updates, Sicherung, Deinstallation, Fehlersuche, Dateiablage.
Inhalte aus dem Quelltext gezogen, nicht geschaetzt: Abklingzeiten,
Tonspuren, Ausgabedateien, Routen der Einrichtungsseite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Wer das Toolkit testweise installiert hatte, konnte es nur von Hand wieder
abraeumen - zwei Aufgaben im Aufgabenplaner, ein laufender Prozess, zwei
Ordner. Das ist jetzt ein Skript.
Die Daten bleiben per Vorgabe stehen: in der Datenbank liegen Chatverlaeufe,
und die sind nach einer versehentlichen Deinstallation weg. -AuchDaten
raeumt auch die ab.
Getestet an einer laufenden Installation: Aufgaben weg, Prozess beendet,
Programm geloescht, Port 4792 frei.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Zwei Fehler, beide in scripts/install.ps1:
1. Die Hilfsfunktion Ruf gab die Programmausgabe mit zurueck. `return
$LASTEXITCODE` nach `& $Datei` liefert ein Array aus allen Ausgabe-
zeilen plus Exit-Code - jeder Vergleich damit ist falsch. Deshalb galt
ein erfolgreiches fetch-model als fehlgeschlagen und die check-env-
Ausgabe verschwand. Jetzt ueber Out-Host.
2. Die Bereitschaftspruefung fragte http://localhost:4792/health ab. Der
Daemon lauscht nur auf IPv4, Windows loest localhost aber zuerst nach
::1 auf: gemessen 2066 ms bis zum Rueckfall gegenueber 15 ms direkt.
Bei einem Zeitlimit von 2 s lief die Pruefung jedes Mal ab, obwohl der
Daemon laengst lief. Jetzt 127.0.0.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Die Aufgabe scheiterte mit Code 1, abdulive.log gab es gar nicht. Ursache:
& $node $main ingest 2>&1 | ForEach-Object { ... }
Windows PowerShell 5.1 verpackt jede stderr-Zeile eines nativen Programms
in einen ErrorRecord. Zusammen mit $ErrorActionPreference = Stop beendet
das sofort das ganze Skript - und der Daemon schreibt im Normalbetrieb
Warnungen auf stderr ("Kein Broadcaster-Token ..."). Er starb also an
seiner eigenen ersten Meldung, bevor irgendetwas ins Protokoll kam.
- launcher.ps1 startet jetzt ueber Start-Process mit getrennten
Umleitungen fuer stdout und stderr, je Lauf rotiert
- install.ps1 ruft native Programme ueber eine Hilfsfunktion auf, die
ErrorActionPreference kurz zurueckstellt
- NODE_OPTIONS=--disable-warning=ExperimentalWarning fehlte im Paket; im
Container stand es in der Dockerfile
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Vorher endete die Installation mit drei Befehlen zum Abtippen. Es gibt
keinen Grund dafuer - der Installer laedt jetzt das Sprachmodell, laeuft
den Selbsttest, startet den Daemon, wartet auf /health und oeffnet die
Einrichtungsseite.
Ein fehlgeschlagener Modell-Download bricht nichts ab: das Toolkit laeuft
auch ohne, es entstehen dann Clips ohne Untertitel. Schalter -OhneModell
und -KeinBrowser fuer den Fall, dass man das nicht will.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Die Vorgabe fuer -Quelle zeigt jetzt auf die gepruefte Veroeffentlichung.
Damit reicht ein Befehl:
irm .../scripts/install.ps1 | iex
Die Pruefung auf HTML statt JSON bleibt drin - sie war der Grund, warum
der erste Versuch als PowerShell-Syntaxfehler endete statt als Fehlermeldung.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Das Repo ist jetzt oeffentlich, die Pakete haengen an der Veroeffentlichung
`latest`. Damit funktioniert `abdulive update` ohne Token.
- config.example.yaml traegt die gepruefte Update-Adresse
- .gitea/workflows/release.yml baut beim Tag und ersetzt `latest`
- Byte Order Mark: Notepad und `Set-Content -Encoding utf8` schreiben eines,
und ohne Behandlung hiess der erste Schluessel in der config.yaml
"\ufeffchannel" - die Einstellung waere stillschweigend ignoriert worden
- Der YAML-Leser hatte gar keine Tests; er liest die Konfiguration, an der
alles haengt. Jetzt acht.
Der Updater ist einmal komplett durchgelaufen: 0.4.0 erkannt, 119 MB geladen,
Pruefsumme geprueft, bereitgelegt, von launcher.ps1 getauscht.
93 Tests, Typpruefung sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
Docker faellt weg. Das Projekt hat keine npm-Abhaengigkeiten und Node fuehrt
TypeScript direkt aus - der Container schrieb nur noch drei Programme fest,
und dafuer braucht es keine Virtualisierung, sondern einen Ordner.
Der Anlass war, dass ADR-0004 zwar `docker compose pull` versprach, es aber
nie ein veroeffentlichtes Image gab. Wer installieren wollte, musste
whisper.cpp aus dem Quelltext uebersetzen.
Plattformschicht (packages/core/src/platform.ts):
- Programme werden neben der Anwendung gesucht, nicht im PATH
- Daten unter %LOCALAPPDATA%, damit Installation und Update ohne
Administratorrechte auskommen
- Schriften mitgeliefert: ohne fontsdir setzt libass wortlos eine
Ersatzschrift ein - gemessen 720 statt 826 Pixel Zeilenbreite
Windows-Eigenheiten, an echtem ffmpeg 8.1 durchprobiert:
- Pfade im Filtergraphen brauchen alle drei Schritte (Vorwaertsschraegstriche,
escapter Doppelpunkt, Anfuehrungszeichen); je zwei davon reichen nicht
- `nice` gibt es nicht, ersetzt durch os.setPriority
Auslieferung:
- scripts/build-package.ts baut das Paket, laeuft auch unter Linux
- install.ps1 ohne Administratorrechte, Autostart ueber den Aufgabenplaner
- update legt neue Fassungen bereit; eingespielt wird beim naechsten Start,
nie mitten im Stream
- ZIP lesen und schreiben ohne Abhaengigkeiten (packages/core/src/zip.ts)
Kein `irm ... | iex`-Einzeiler: git.zeasy.dev antwortete instanzweit mit einer
Umleitung auf /user/login, und `iex` fuehrte die Anmeldeseite als PowerShell
aus. Der Updater verfolgt Umleitungen jetzt nicht und hat keine erfundene
Vorgabeadresse mehr.
Ausserdem: backup.ps1 war an zwei Stellen dokumentiert, aber nie geschrieben.
ADR-0007 ersetzt ADR-0004. 84 Tests, Typpruefung sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
whisper.cpp laeuft ueber die Tonspur und liefert Tokens; daraus werden
Woerter mit Zeitstempeln (Wortstuecke wie "Sav"+"ina" wieder zusammengesetzt)
und daraus kurze Zeilen im ASS-Format mit wortweiser Hervorhebung. SRT und
Transkript fallen nebenbei ab.
An einem echten deutschen Clip geprueft. Drei Fehler kamen erst dabei heraus:
Zeilen mit einem einzigen Wort bei zoegerndem Sprechen, ein Nachlauf, der in
die naechste Zeile ragte (ASS stapelt gleichzeitige Zeilen uebereinander),
und ein Uebertragsfehler im Zeitformat bei 59,999 s.
Layout wird jetzt VORGEGEBEN statt nachgemessen: ein Vergleich zweier
Aufnahmen zeigte, dass die Facecam von Stream zu Stream anders liegt. Das
Profil "default" beschreibt die Vorgabe (Facecam 480x270 oben links, Chat ab
x=1405, Gameplay-Band 890 breit mittig auf x=960), "bestand-2026-09" haelt
den alten Zustand fuer altes Material fest. Dazu eine Ausrichtungsvorlage als
Bild, die in OBS ueber die Szene gelegt wird, und docs/obs-layout.md.
Befund am echten Clip: im Streambild sind bereits zwei Textebenen eingebrannt
(Spieluntertitel und ein Live-Caption-Overlay). Beides ist im Layout-Dokument
behandelt.
53 Tests gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
Aus der Rohdatei entsteht das fertige Hochformat. Der Filtergraph liegt jetzt
in packages/media und wird von calibrate und vom Render-Schritt gemeinsam
benutzt - die Zahlen darin sind eingemessen, sie gehoeren an genau eine Stelle.
Eine Aufgabe nach der anderen, und waehrend gesendet wird standardmaessig
gar nicht (renderWhileLive: false). ffmpeg laeuft unter nice, mit Zeitlimit,
und die letzten Zeilen von stderr landen in der Fehlermeldung statt im Nichts.
Aus dem 90-Sekunden-Puffer werden die letzten 45 Sekunden genommen (-sseof vor
-i, sonst wird die ganze Datei dekodiert und danach verworfen).
Dabei einen eigenen Fehler aus S2 korrigiert: die Rohdatei wurde in den
Windows-Bind-Mount kopiert und dort verarbeitet - genau das, was ADR-0004
Punkt 2 verbietet. Jetzt liegt sie im Arbeits-Volume, auf die Windows-Seite
kommt nur das fertige, kleine Ergebnis.
Verifiziert an einer kuenstlichen Aufnahme mit drei getrennten Tonspuren:
1920x1080/70 s hinein, 1080x1920/45 s heraus, Naht an der richtigen Stelle,
27,9 s Rechenzeit unter nice 15. 39 Tests gruen.
Neuer Befehl `render <datei>` zum Nachrendern nach Layout-Aenderungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
Eigener obs-websocket-v5-Client (das Protokoll ist klein genug, und das
Projekt bleibt ohne Abhaengigkeiten). Anmeldung, Wiederverbindung mit
wachsender Pause, Ereignisse fuer ReplayBufferSaved und Szenenwechsel.
Der Clipper loest zwei Wege gleichzeitig aus: OBS-Wiedergabepuffer fuer die
Rohdatei in Aufnahmequalitaet, Twitch-Clip fuer die teilbare URL. Faellt
einer aus, ist der andere trotzdem etwas wert. Ein direkt in OBS gedrueckter
Hotkey wird ohne unseren Umweg erkannt und als vollwertiger Ausloeser
behandelt.
Die Pfaduebersetzung Windows -> Container hat eine eigene Datei und eigene
Tests - das ist die Stelle, an der die Pipeline am ehesten scheitert. Dazu
Warten auf den abgeschlossenen Schreibvorgang, bevor die Datei uebernommen
wird.
Zum Testvektor der OBS-Anmeldung: die Spezifikation nennt Salt und Challenge,
aber kein erwartetes Ergebnis. Der Erwartungswert wurde unabhaengig mit
Pythons hashlib aus derselben Beschreibung gerechnet; der Test sagt das im
Kommentar, statt einen amtlichen Vektor vorzutaeuschen.
29 Tests gruen. Die Abnahme von S2 braucht ein laufendes OBS und steht aus.
Ausserdem: ADR-0006 haelt fest, dass alles unter einem Dach laeuft - eine
lokale Weboberflaeche plus Kindprozesse fuer Rechenarbeit. Die Prozessliste
in der Architekturdoku ist entsprechend ersetzt. Der Onboarding-Assistent
ist als Teil von S5 aufgenommen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
Zugangsdaten kommen jetzt aus der App statt aus einer .env: ein
Einrichtungs-Assistent auf http://localhost:4792/setup nimmt Client-ID,
Secret und OBS-Passwort entgegen, der OAuth-Rueckruf laeuft auf denselben
Server. Gespeichert wird verschlueselt (AES-256-GCM) in einem Volume,
bewusst nicht in der Datenbank - die wird auf die Windows-Seite gesichert.
Siehe ADR-0005.
Technikbasis vereinfacht: Node 24 bringt node:sqlite, WebSocket, fetch und
die Ausfuehrung von TypeScript mit. Das Projekt hat damit null
Laufzeitabhaengigkeiten und keinen Build-Schritt; pnpm-Workspaces und die
Paketmanifeste sind entfallen, Importe laufen ueber die imports-Map der
Wurzel-package.json. erasableSyntaxOnly verhindert Syntax, die das Type
Stripping stillschweigend entfernen wuerde. Siehe Nachtrag 2 in ADR-0001.
Enthalten:
- packages/core: Config, Layout, Logger mit Secret-Maskierung, Retry, YAML
- packages/db: Migrationen, Repositories, alle SQL-Anweisungen an einem Ort
- packages/twitch: Secret-Speicher, OAuth, Helix mit Token-Bucket, IRC
- apps/ingest: Daemon, Einrichtungsseite, Spike-Erkennung
- 20 Tests (IRC-Parser, Spike-Detektor, Datenbankschicht)
Der Chat verbindet sich anonym, solange nichts hinterlegt ist - damit laesst
sich die Datenaufnahme pruefen, bevor die Twitch-Apps existieren.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
Image 258 MB mit Node 22.23, ffmpeg 5.1.9 (libx264, libass) und
whisper.cpp v1.7.4. check-env laeuft ohne harte Fehler durch; calibrate
erzeugt aus einem 1920x1080-Bild die 1080x1920-Komposition mit den
eingemessenen Ausschnitten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB
Monorepo-Geruest fuer neun Streamer-Tools plus den Ingest-Daemon, der
unter sechs davon liegt. Enthaelt:
- docs/: Ueberblick, Architektur, Twitch-Integration, Datenmodell,
Roadmap, Setup, Hebel-Analyse und den MVP-Plan "Clip-Knopf"
- docs/projects/: je eine Spezifikation pro Tool
- docs/decisions/: vier ADRs (TS-Monorepo, SQLite, EventSub/IRC, Docker)
- docker/: Image mit ffmpeg und whisper.cpp, abhaengigkeitsfreier
Entrypoint mit check-env, fetch-model und calibrate
- config/layout.yaml: aus einem VOD-Screenshot eingemessener
Facecam-Ausschnitt (440x330 oben links, Chat-Overlay ab x=1270)
Noch kein Anwendungscode - Scheibe S0 aus docs/07-mvp-plan.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013VTGjd92LsDDWF3frd9FTB