„-z 0-05:00“ bedeutet fünf Stunden und bearbeitete bisher stillschweigend das ganze Video. vtt2txt.py (und damit extract_text.py und abschrift_korrigieren.py) nennt jetzt das Ende der Untertitel, bei Bereichen ab einer Stunde mit dem Tipp „Minute 5 ist 00:05“. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
514 lines
27 KiB
Markdown
514 lines
27 KiB
Markdown
# youtube_abschrift_korrigieren
|
||
|
||
Korrigiert automatische Abschriften gesprochener Beiträge – Reden, Vorträge,
|
||
Interviews, Videos, auf Deutsch oder Englisch (z. B. YouTube-Untertitel als Text) –
|
||
mit einem lokalen Sprachmodell über Ollama: Rechtschreibung, Satzzeichen,
|
||
Hörfehler, Absätze – möglichst wortgetreu, ohne Zusammenfassung oder Glättung.
|
||
Das Hauptprogramm ist `abschrift_korrigieren.py`.
|
||
|
||
Eine ausführliche Anleitung für die Benutzung Schritt für Schritt, mit
|
||
Arbeitsablauf, Glossar-Pflege, Prüfung der Ergebnisse und Fehlerbehebung, steht in
|
||
der [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md). Diese README ist die knappe
|
||
technische Übersicht.
|
||
|
||
Die Werkzeuge sind einzeln oder hintereinander nutzbar:
|
||
|
||
| Skript | Aufgabe |
|
||
|---|---|
|
||
| `extract_text.py` | YouTube-URL → Textdatei in einem Schritt (die beiden folgenden zusammen) |
|
||
| `yt_untertitel.py` | Untertitel eines YouTube-Videos als VTT herunterladen |
|
||
| `vtt2txt.py` | VTT in eine Rohabschrift (Text) umwandeln |
|
||
| `abschrift_korrigieren.py` | Rohabschrift (oder VTT direkt) mit dem Sprachmodell korrigieren |
|
||
| `aenderungen.py` | Änderungsbericht: was hat das Modell gegenüber dem Rohtext geändert? |
|
||
| `glossar_vorschlag.py` | Glossar-Entwurf: vermutete Hörfehler bei Namen und Begriffen |
|
||
|
||
## Voraussetzungen
|
||
|
||
- Python 3.9+ (nur Standardbibliothek)
|
||
- für den Download: [yt-dlp](https://github.com/yt-dlp/yt-dlp) (`pip install yt-dlp`)
|
||
- Ollama auf `127.0.0.1:11434` mit dem Modell `qwen3:32b`
|
||
(`ollama pull qwen3:32b`)
|
||
`abschrift_korrigieren.py` und `glossar_vorschlag.py` prüfen beides vor dem Start und
|
||
brechen sonst mit einem Hinweis ab (`sudo systemctl start ollama` bzw. `ollama pull …`)
|
||
- für den Änderungsbericht eine Wortliste: `/usr/share/dict/ngerman` (Paket `wngerman`),
|
||
für englische Beiträge `/usr/share/dict/american-english` (Paket `wamerican`).
|
||
Fehlt sie, gibt es einen Hinweis und die Prüfung „gültiges Wort ersetzt“ entfällt
|
||
|
||
Für gute Geschwindigkeit muss das Modell komplett in den Grafikspeicher passen.
|
||
Mit `OLLAMA_NUM_PARALLEL=1` braucht `qwen3:32b` bei 16k Kontext rund 24 GB
|
||
(eine RTX 3090). Bei `OLLAMA_NUM_PARALLEL=4` landet ein Drittel auf der CPU und
|
||
der Lauf wird etwa 12-mal langsamer.
|
||
|
||
## Aufruf
|
||
|
||
Von der YouTube-URL zum korrigierten Text in einem Befehl:
|
||
|
||
```bash
|
||
./abschrift_korrigieren.py -u 'https://www.youtube.com/watch?v=rm6lADgf6GQ' -o lahnstein.korrigiert.txt
|
||
# → lahnstein.korrigiert.txt, lahnstein.txt (Rohtext), lahnstein.aenderungen.html
|
||
```
|
||
|
||
Oder in zwei Schritten, um den Rohtext vorher anzusehen:
|
||
|
||
```bash
|
||
./extract_text.py -u 'https://www.youtube.com/watch?v=rm6lADgf6GQ' -o lahnstein.txt
|
||
./abschrift_korrigieren.py -i lahnstein.txt # → lahnstein.korrigiert.txt
|
||
```
|
||
|
||
Dasselbe in Einzelschritten, wenn die VTT-Datei erhalten bleiben soll:
|
||
|
||
```bash
|
||
./yt_untertitel.py -u 'https://www.youtube.com/watch?v=rm6lADgf6GQ' -n Lahnstein -d testdaten
|
||
./vtt2txt.py -i testdaten/Lahnstein.de-orig.vtt
|
||
./abschrift_korrigieren.py -i testdaten/Lahnstein.de-orig.txt
|
||
```
|
||
|
||
Der Schritt über `vtt2txt.py` ist optional: `abschrift_korrigieren.py` nimmt auch die
|
||
`.vtt` direkt. Die `.txt` lohnt sich, um den Rohtext anzusehen oder ein Glossar
|
||
daraus zu erstellen.
|
||
|
||
### URL → Text in einem Schritt: `extract_text.py`
|
||
|
||
Lädt die Untertitel und speichert nur den Text; die VTT-Datei bleibt ein
|
||
Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei
|
||
`yt_untertitel.py`, nur ist die Standardsprache die des Original-Audios.
|
||
|
||
```bash
|
||
./extract_text.py -u 'https://www.youtube.com/watch?v=0LmCveTM788'
|
||
# → John_Mearsheimer_Offensive_Realism_Episode_1_Origins_of_the_War_In_Ukraine_2026-07-30.txt
|
||
./extract_text.py -u URL -l de -o testdaten/rede.txt # Sprache und Ziel
|
||
./extract_text.py -u URL -f -o rede.txt # lesbarer Fließtext
|
||
./extract_text.py -u URL -z 0:03:17-0:05:25 # nur ein Ausschnitt
|
||
./extract_text.py -u URL -z 00:05-00:06 00:10-00:12 00:25 # mehrere Bereiche
|
||
./abschrift_korrigieren.py -i rede.txt # → rede.korrigiert.txt
|
||
```
|
||
|
||
| Option | Bedeutung |
|
||
|---|---|
|
||
| `-u`, `--url` | YouTube-URL, Pflicht |
|
||
| `-l`, `--lang` | Sprache; Standard: Sprache des Original-Audios (`<sprache>-orig`) |
|
||
| `-o`, `--output` | Ausgabedatei (Verzeichnisse werden angelegt, vorhandene Datei überschrieben); Standard: `<Kanal>_<Titel>_<Datum>.txt`, siehe unten |
|
||
| `-z`, `--zeitraeume` | nur Untertitel in diesen Bereichen, siehe [Zeitauswahl](#zeitauswahl--z) |
|
||
| `--exakt` | genau an den Zeiten schneiden statt an Satzgrenzen |
|
||
| `-f`, `--fliesstext` | Fließtext, auf 80 Zeichen umbrochen (Wörter werden nie getrennt), statt der kurzen Untertitelzeilen; beides versteht `abschrift_korrigieren.py` |
|
||
|
||
### Standard-Dateinamen für YouTube-Videos
|
||
|
||
Ohne `-o` bzw. `-n` bilden `extract_text.py`, `yt_untertitel.py` und
|
||
`abschrift_korrigieren.py -u` den Namen aus Kanal, Titel und Hochladedatum:
|
||
|
||
| Video | Name |
|
||
|---|---|
|
||
| John Mearsheimer, „Episode 1: Origins of the War In Ukraine“ | `John_Mearsheimer_Offensive_Realism_Episode_1_Origins_of_the_War_In_Ukraine_2026-07-30` |
|
||
| GGBLahnstein, „Prof. Gabriele Krone-Schmalz – Respekt geht anders – …“ | `GGBLahnstein_Prof_Gabriele_Krone-Schmalz_Respekt_geht_anders_2026-07-30` |
|
||
|
||
Wörter werden mit `_` verbunden. Bindestriche und Punkte im Wort bleiben
|
||
(„Krone-Schmalz“), andere Zeichen fallen weg. Der Kanal wird auf 40, der Titel auf
|
||
60 Zeichen an einer Wortgrenze gekürzt. Ohne verwertbaren Kanal und Titel gilt die
|
||
Video-ID. Mit `-z` kommen die Bereiche dazu, z. B.
|
||
`…_2026-07-30_00-03-17_bis_00-05-25` (`-z A`: `_ab_…`, `-z 0-B` bzw. `-z -B`: `_bis_…`;
|
||
mehrere Bereiche mit `+` verbunden: `…_00-05-00_bis_00-06-00+ab_00-25-00`).
|
||
Gleiches Video ergibt immer denselben Namen; eine vorhandene Datei wird
|
||
überschrieben (ein mit `-u` gespeicherter, abweichender Rohtext aber nie).
|
||
|
||
### Untertitel herunterladen: `yt_untertitel.py`
|
||
|
||
Lädt die Untertitel eines YouTube-Videos über `yt-dlp` als VTT-Datei, ohne das
|
||
Video selbst. Gewählt wird:
|
||
|
||
1. manuelle Untertitel in der gewünschten Sprache, sonst
|
||
2. automatische Untertitel in der Originalsprache des Videos (`de-orig`).
|
||
|
||
Automatische Untertitel ohne `-orig` sind Maschinenübersetzungen aus einer anderen
|
||
Sprache und werden nie genommen. Gibt es keine passende Spur, nennt das Skript die
|
||
vorhandenen Sprachen.
|
||
|
||
Videos mit KI-synchronisierten Tonspuren haben für *jede* Synchronsprache eine
|
||
`-orig`-Spur. Die Originalsprache kommt deshalb aus den Video-Infos (`language`, sonst
|
||
die Tonspur mit dem Vermerk „original“); automatische Untertitel anderer Sprachen
|
||
gehören zur Synchronisation und werden nicht genommen. Sprachen werden auch mit
|
||
Region gefunden: `-l en` findet manuelle Untertitel `en-US`.
|
||
|
||
```bash
|
||
./yt_untertitel.py -u URL # → <Kanal>_<Titel>_<Datum>.de-orig.vtt
|
||
./yt_untertitel.py -u URL -n Lahnstein -d testdaten # → testdaten/Lahnstein.de-orig.vtt
|
||
```
|
||
|
||
| Option | Bedeutung |
|
||
|---|---|
|
||
| `-u`, `--url` | YouTube-URL, Pflicht |
|
||
| `-n`, `--name` | Dateiname ohne Endung; Standard: `<Kanal>_<Titel>_<Datum>` |
|
||
| `-l`, `--lang` | Sprache; Standard: `de` |
|
||
| `-d`, `--dir` | Zielverzeichnis (wird angelegt); Standard: aktuelles Verzeichnis |
|
||
|
||
Eine vorhandene Datei gleichen Namens wird überschrieben.
|
||
|
||
### Untertitel umwandeln: `vtt2txt.py`
|
||
|
||
Macht aus VTT-Untertiteln (z. B. von `yt-dlp --write-auto-subs`) die Rohabschrift
|
||
für `abschrift_korrigieren.py`: Zeitstempel und Tags werden entfernt. YouTube-Auto-Untertitel
|
||
zeigen ein rollendes Fenster, jeder Block wiederholt das Ende des vorigen. Übernommen
|
||
wird nur, was nach dieser Überlappung neu ist; sagt der Redner eine Zeile wirklich
|
||
zweimal, bleibt sie zweimal erhalten. Ob eine Datei rollt, wird pro Datei bestimmt:
|
||
Wiederholt mindestens die Hälfte der Blöcke das Ende des vorigen (Auto-Untertitel:
|
||
über 95 %), gilt sie als rollend. Sonst, etwa bei manuell erstellten Untertiteln,
|
||
wird jeder Block vollständig übernommen.
|
||
|
||
```bash
|
||
./vtt2txt.py -i rede.de.vtt # → rede.de.txt
|
||
./vtt2txt.py -i ramstein.de*.vtt # mehrere Dateien, je → *.txt
|
||
./vtt2txt.py -i "ramstein.de*.vtt" # Muster löst das Programm auf (Windows)
|
||
./vtt2txt.py -i rede.de.vtt -o out/rede.txt # eigene Ausgabedatei
|
||
./vtt2txt.py -i *.vtt -o out/ # alle in ein Verzeichnis
|
||
./vtt2txt.py -i rede.de.vtt -z 0:03:17-0:05:25 # Ausschnitt → rede.de_00-03-17_bis_00-05-25.txt
|
||
./vtt2txt.py -i rede.de.vtt -z 00:05-00:06 00:25 # mehrere Bereiche in einer Datei
|
||
```
|
||
|
||
| Option | Bedeutung |
|
||
|---|---|
|
||
| `-i`, `--input` | eine oder mehrere VTT-Dateien oder Muster, Pflicht |
|
||
| `-o`, `--output` | Ausgabedatei (nur bei einer Eingabedatei) oder Verzeichnis (vorhanden oder mit `/` am Ende); Standard: `<Eingabe ohne Endung>.txt` neben der Eingabe, mit `-z` zusätzlich die Bereiche im Namen |
|
||
| `-z`, `--zeitraeume` | nur Untertitel in diesen Bereichen, siehe unten |
|
||
| `--exakt` | genau an den Zeiten schneiden, auch mitten im Satz |
|
||
|
||
#### Zeitauswahl (`-z`)
|
||
|
||
Gilt gleich für `vtt2txt.py`, `extract_text.py` und `abschrift_korrigieren.py`.
|
||
|
||
| Zeit | Bedeutung |
|
||
|---|---|
|
||
| `00:05:10` | Stunden:Minuten:Sekunden (auch `00:5:10`, Sekunden mit Nachkommastellen) |
|
||
| `00:20` | Stunden:Minuten, also Minute 20 |
|
||
| `25` | Minute 25 |
|
||
|
||
Achtung: Zwei Zahlen sind Stunden:Minuten, `05:00` ist also Stunde 5, nicht
|
||
Minute 5 (dafür `00:05` oder `5`). Reicht ein Bereich über die letzte
|
||
Untertitelzeile hinaus, nennt ein Hinweis das Ende der Untertitel, bei Bereichen ab
|
||
einer Stunde mit diesem Tipp.
|
||
|
||
| Aufruf | Text |
|
||
|---|---|
|
||
| `-z 00:05:10` | ab 5:10 bis zum Ende des Videos |
|
||
| `-z 0-00:20` oder `-z -00:20` | vom Anfang bis Minute 20 |
|
||
| `-z 00:05-00:06` | von Minute 5 bis Minute 6 |
|
||
| `-z 00:5:00-00:6:00 00:10-00:12:00 00:25` | Minute 5–6, Minute 10–12 und alles ab Minute 25 |
|
||
|
||
Ein Bereich ist `A-B`, `A` (ab A bis zum Ende) oder `0-B` bzw. kurz `-B` (vom
|
||
Anfang bis B). Mehrere Bereiche werden durch Leerzeichen getrennt, mit oder ohne
|
||
Anführungszeichen (`-z "00:05-00:06 00:25"` ist dasselbe). Sie dürfen sich
|
||
nicht überschneiden und werden nach der Zeit sortiert.
|
||
|
||
Alle Bereiche landen in **einer** Datei. Vor jedem Bereich steht eine Überschrift
|
||
mit seinen tatsächlichen Zeiten:
|
||
|
||
```text
|
||
[00:04:57 - 00:06:04]
|
||
Ihr seht, es gibt keinen einzigen Dialog …
|
||
|
||
[00:09:58 - 00:12:03]
|
||
Also, wenn ihr Programme baut mit Claude Code, …
|
||
```
|
||
|
||
Angeschnittene Sätze werden an beiden Enden vollständig aufgenommen. Der
|
||
Ausschnitt beginnt nach dem letzten Satzende vor dem Start und endet mit dem
|
||
ersten Satzende nach dem Ende, höchstens 30 Sekunden darüber hinaus
|
||
(`MAX_SNAP_SECONDS`). Liegt kein Satzende in Reichweite, wird an dieser Seite
|
||
genau geschnitten (`--exakt` schneidet immer genau).
|
||
|
||
Die Zeiten der Überschrift sind die Start- und Endzeit der ersten bzw. letzten
|
||
Untertitelzeile, also auf wenige Sekunden genau. Überschneiden sich zwei
|
||
Bereiche erst durch die Satz-Erweiterung, werden sie zu einem Block
|
||
zusammengefasst. Bereiche ohne Untertitel werden mit einem Hinweis übersprungen.
|
||
Der Dateiname enthält immer die angegebenen Zeiten.
|
||
|
||
Eine fehlerhafte Datei (keine Untertitel, nicht UTF-8) wird gemeldet, die übrigen
|
||
werden trotzdem umgewandelt; der Exit-Code ist dann 1.
|
||
|
||
### Abschrift korrigieren: `abschrift_korrigieren.py`
|
||
|
||
```bash
|
||
./abschrift_korrigieren.py -i rede.txt # → rede.korrigiert.txt
|
||
./abschrift_korrigieren.py -i rede.txt -o out/rede.txt # eigene Ausgabedatei
|
||
./abschrift_korrigieren.py -i rede.txt -g rede.glossar.txt # mit Glossar
|
||
./abschrift_korrigieren.py -i rede.txt -g allgemein.glossar.txt -g rede.glossar.txt # kombiniert
|
||
./abschrift_korrigieren.py -i rede.de.vtt # VTT direkt → rede.de.korrigiert.txt
|
||
./abschrift_korrigieren.py -u URL # YouTube → <Kanal>_<Titel>_<Datum>.korrigiert.txt
|
||
./abschrift_korrigieren.py -i talk.txt -l en # englischer Beitrag
|
||
./abschrift_korrigieren.py -u URL -z 0:03:17-0:05:25 # nur ein Ausschnitt des Videos
|
||
./abschrift_korrigieren.py -u URL -z 00:05-00:06 00:25 # mehrere Bereiche, eine Datei
|
||
```
|
||
|
||
| Option | Bedeutung |
|
||
|---|---|
|
||
| `-i`, `--input` | Eingabedatei: Rohabschrift (`.txt`) oder Untertitel (`.vtt`, wird wie mit `vtt2txt.py` umgewandelt) |
|
||
| `-u`, `--url` | statt `-i`: YouTube-URL; Untertitel wie bei `extract_text.py`. Der Rohtext wird neben der Ausgabe gespeichert (`rede.korrigiert.txt` → `rede.txt`, sonst `<Name>.roh.txt`), eine vorhandene, abweichende Datei aber nie überschrieben |
|
||
| `-l`, `--lang` | Sprache des Beitrags: `de` oder `en` (auch mit Region, z. B. `en-US`); wählt Prompts und Prüfmarke. Mit `-u` zugleich die Sprache der Untertitel. Standard: `de`, mit `-u` die Sprache des Original-Audios |
|
||
| `-o`, `--output` | Ausgabedatei; Standard: `<Eingabe ohne Endung>.korrigiert.txt` neben der Eingabe, mit `-u` `<Kanal>_<Titel>_<Datum>.korrigiert.txt`; mit `-z` zusätzlich die Bereiche im Namen |
|
||
| `-z`, `--zeitraeume` | nur diese Bereiche korrigieren, siehe [Zeitauswahl](#zeitauswahl--z); nur mit `.vtt`-Eingabe oder `-u`, da reiner Text keine Zeitstempel hat |
|
||
| `--exakt` | genau an den Zeiten schneiden statt an Satzgrenzen |
|
||
| `-g`, `--glossar` | Glossar mit festen Ersetzungen und Schreibweisen; mehrfach angeben zum Kombinieren (Ersetzungen in Reihenfolge der Angabe) |
|
||
| `-m`, `--model` | Ollama-Modell; Standard: `qwen3:32b` (Konstante `MODEL`) |
|
||
| `--kein-bericht` | keinen Änderungsbericht schreiben |
|
||
| `--ueberschreiben` | Ausgabedatei auch ersetzen, wenn sie seit dem letzten Lauf von Hand geändert wurde |
|
||
|
||
Eine von Hand nachbearbeitete Ausgabedatei überschreibt ein erneuter Lauf nicht: Das
|
||
Ergebnis kommt dann nach `<Name>.neu.korrigiert.txt`, mit Warnung. Erkannt wird das
|
||
an der Prüfsumme der zuletzt geschriebenen Ausgabe in `<Ausgabe>.parts/ausgabe.sha256`;
|
||
ohne Prüfsumme gilt jede abweichende vorhandene Datei als von Hand geändert.
|
||
|
||
Mit Zeitauswahl wird jeder Bereich als eigener Block korrigiert und gegliedert;
|
||
die Überschriften `[00:04:57 - 00:06:04]` bekommt das Modell nicht zu sehen, sie
|
||
stehen unverändert als eigene Zeilen im Ergebnis. Zusätzlich weist der Prompt das
|
||
Modell an, an den Rändern der Ausschnitte nichts zu ergänzen. Eine Rohabschrift
|
||
(`.txt`) mit solchen Überschriften, etwa von `vtt2txt.py -z`, wird ebenso in
|
||
Blöcken korrigiert.
|
||
|
||
## Ablauf
|
||
|
||
0. **Vorprüfung:** Läuft Ollama, ist das Modell installiert? Sonst Abbruch mit
|
||
Hinweis, noch vor jedem Download.
|
||
1. **Fließtext:** Untertitel-Zeilenumbrüche werden entfernt. Zeit-Überschriften
|
||
(`-z`) teilen den Text in Blöcke, die einzeln bearbeitet werden.
|
||
2. **Feste Ersetzungen** aus dem Glossar (`falsch => richtig`) werden im Rohtext
|
||
angewendet, bevor das Modell ihn sieht.
|
||
3. **Korrektur:** Der Text wird in Abschnitte von ca. 1200 Wörtern geteilt (immer
|
||
am Satzende; Abkürzungen wie „z. B.“, „Dr.“, „bzw.“ und Ordnungszahlen wie
|
||
„2. Weltkrieg“ zählen nicht als Satzende) und abschnittsweise vom Modell
|
||
korrigiert. Fortschritt wird live angezeigt.
|
||
4. **Wortzahl-Prüfung:** Weicht die Wortzahl eines Abschnitts um mehr als 5 % ab,
|
||
gibt es eine Warnung (mögliche Auslassungen oder Ergänzungen).
|
||
5. **Absätze:** Das Modell nennt nur Satznummern, an denen ein Absatz beginnt; der
|
||
Text selbst wird dabei nicht verändert. Absätze über 10 Sätze werden nochmals
|
||
geteilt, Absätze unter 3 Sätzen mit einem Nachbarn verbunden. Passt der kurze
|
||
Absatz an beide Nachbarn, entscheidet das Modell nach dem Inhalt, ob er den
|
||
vorherigen Gedanken abschließt oder den folgenden einleitet.
|
||
6. **Änderungsbericht:** Rohtext (vor dem Glossar) und Ergebnis werden Wort für Wort
|
||
verglichen; eigenmächtige Änderungen des Modells kommen in die Liste
|
||
„Zu prüfen“, siehe unten.
|
||
|
||
Zweifelhafte Stellen markiert das Modell als `Lesart [prüfen: Originalwortlaut]`,
|
||
in englischen Reden als `reading [check: original wording]`.
|
||
|
||
Englische Reden bekommen eigene, englische Prompts für Korrektur und Absätze
|
||
(`SYSTEM_EN` usw. in `abschrift_korrigieren.py`); dort entfernt das Modell auch die
|
||
Sprecherwechsel-Marken `>>` der YouTube-Auto-Untertitel. Weitere Sprachen lassen sich
|
||
über das Wörterbuch `PROMPTS` ergänzen.
|
||
|
||
## Änderungsbericht
|
||
|
||
Jeder Lauf von `abschrift_korrigieren.py` schreibt neben die Ausgabe
|
||
`<Name>.aenderungen.html` (aus `rede.korrigiert.txt` wird `rede.aenderungen.html`)
|
||
und zeigt die häufigsten Änderungen in der Konsole. Der Bericht enthält:
|
||
|
||
- **Zu prüfen:** eigenmächtige Änderungen des Modells, die den Sinn ändern können,
|
||
mit Grund und Zusammenhang (siehe unten),
|
||
- alle Änderungen nach Häufigkeit (`gedeiliche → gedeihliche`, `∅ → ist` für Einfügungen),
|
||
- den korrigierten Text mit seinen Absätzen: ersetzte Wörter gelb mit dem Original
|
||
dahinter, Einfügungen grün, Streichungen rot durchgestrichen, `[prüfen: …]` orange,
|
||
Stellen „zu prüfen“ rot umrahmt (gestrichene als ⚠, Grund beim Darüberfahren).
|
||
|
||
Nicht markiert werden Groß-/Kleinschreibung, Satzzeichen (auch eingefügte
|
||
Gedankenstriche) und Schreibweisen wie „z.B.“ → „z. B.“. So bleiben die Wortänderungen
|
||
übrig, die man prüfen sollte, gerade eigenmächtige wie „gedeiliche“ → „geregelte“.
|
||
|
||
### Zu prüfen
|
||
|
||
Verglichen wird hier der Text, den das Modell bekommen hat (nach den festen
|
||
Ersetzungen des Glossars), mit seinem Ergebnis. Gemeldet werden:
|
||
|
||
| Grund | Beispiel |
|
||
|---|---|
|
||
| gültiges Wort ersetzt | „Professor“ → „Professorin“, „unterschreiben“ → „unterscheiden“ (das alte Wort steht in der Wortliste, war also kein Hörfehler) |
|
||
| stark verändert | „CT1“ → „ZDF“ (Ähnlichkeit unter 60 %) |
|
||
| eingefügt | „∅“ → „ist“ |
|
||
| gestrichen | „Sicherungskonzepte geht“ → „∅“ (Füllwörter wie „äh“ nicht) |
|
||
|
||
Nicht gemeldet werden Hörfehler-Korrekturen, die ähnlich bleiben
|
||
(„Gradwanderung“ → „Gratwanderung“), reine Zusammen-, Getrennt- und
|
||
Bindestrich-Schreibung („Clan Kriminalität“ → „Clankriminalität“) und Schreibweisen
|
||
aus dem Glossar („Bar“ → „Bahr“ bei „Egon Bahr“ im Glossar): Die neuen Wörter müssen
|
||
zusammenhängend in einem Glossareintrag stehen und dem alten Text ähneln. Einzelne
|
||
Allerweltswörter aus längeren Einträgen („Sie“ aus „finden Sie“) und eingefügte
|
||
Wörter (ein ergänzter Vorname „Alexander“) erklärt das Glossar nicht. Die Liste
|
||
erscheint auch in der Konsole. Kleine Grammatik-Korrekturen wie „das“ → „dass“
|
||
tauchen ebenfalls auf; ein Blick genügt.
|
||
|
||
Für vorhandene Dateien gibt es das eigene Skript. Mit denselben Glossaren wie bei
|
||
der Korrektur gelten deren Ersetzungen und Begriffe nicht als verdächtig:
|
||
|
||
```bash
|
||
./aenderungen.py -a rede.txt -b rede.korrigiert.txt # → rede.aenderungen.html
|
||
./aenderungen.py -a rede.de.vtt -b rede.korrigiert.txt -o bericht.html
|
||
./aenderungen.py -a rede.txt -b rede.korrigiert.txt -g krone-schmalz.glossar.txt -g rede.glossar.txt
|
||
./aenderungen.py -a talk.txt -b talk.korrigiert.txt -l en # englische Wortliste
|
||
```
|
||
|
||
## Glossar
|
||
|
||
Eine Zeile pro Eintrag, `#` leitet Kommentare ein:
|
||
|
||
```text
|
||
# feste Ersetzung (ganze Wörter, Groß-/Kleinschreibung zählt, Reihenfolge = Datei)
|
||
Willly Brand => Willy Brandt
|
||
Kremmel => Kreml
|
||
|
||
# Hinweis fürs Modell (korrekte Schreibweise, optional mit Erläuterung)
|
||
Multi-Domain Task Force
|
||
Michael Gorbatschow (Vorname so belassen)
|
||
```
|
||
|
||
Ersetzungen ohne Treffer werden gesammelt gemeldet. Bei einem Glossar für einen
|
||
einzelnen Beitrag deutet das auf einen Tippfehler hin, bei einem gemeinsamen Glossar ist
|
||
es normal.
|
||
|
||
Mehrere Glossare lassen sich kombinieren: ein gemeinsames für wiederkehrende Namen
|
||
und Hörfehler eines Redners oder Kanals und eines je Beitrag. Beispiel zu den Testdaten:
|
||
`krone-schmalz.glossar.txt` (gemeinsam) plus `ramstein.glossar.txt`,
|
||
`magdeburg.glossar.txt` bzw. `lahnstein.glossar.txt`. Das gemeinsame Glossar zuerst angeben, damit seine
|
||
Ersetzungen vor den speziellen greifen. Ein weiteres Kanal-Glossar:
|
||
`iamfabian.glossar.txt` für Videos über KI-Coding-Tools (Claude Code, Codex, OpenCode …).
|
||
|
||
### Glossar-Entwurf: `glossar_vorschlag.py`
|
||
|
||
Ein Glossar von Hand zu erstellen heißt, den ganzen Rohtext zu lesen. Den ersten
|
||
Entwurf liefert das Skript:
|
||
|
||
1. Das Modell sucht abschnittsweise vermutete Hörfehler: Namen, Orte, Fremdwörter,
|
||
Fachbegriffe und falsch zusammen- oder getrennt geschriebene Wörter. Behalten werden
|
||
nur Funde, die wörtlich im Rohtext stehen; was das Modell für sicher hält, steht
|
||
zuerst.
|
||
2. Mit `-k` kommen die Wörter dazu, die ein Korrekturlauf geändert hat, ohne das,
|
||
was die Glossare ohnehin erledigen. Vieles davon hängt vom Zusammenhang ab
|
||
(`das → dass`), anderes zeigt Fehlgriffe des Modells (`Kremmel → Kremml`).
|
||
|
||
Alle Einträge sind als `# ? falsch => richtig` auskommentiert: Auch „sichere“ Funde
|
||
des Modells sind oft falsch (Lahnstein: `Deckmäntelchen → Deckmäntel`), und eine
|
||
falsche feste Ersetzung verdirbt den Text unbemerkt. Zum Übernehmen `# ? ` entfernen.
|
||
|
||
Einträge aus den mit `-g` angegebenen Glossaren werden vorher angewendet und nicht
|
||
erneut vorgeschlagen. Zeit-Überschriften von Ausschnitten
|
||
(`[00:04:57 - 00:06:04]`, siehe [Zeitauswahl](#zeitauswahl--z)) werden aus
|
||
Roh- und korrigiertem Text entfernt; das Modell bekommt sie nicht zu sehen.
|
||
|
||
```bash
|
||
./glossar_vorschlag.py -i testdaten/Lahnstein.de-orig.txt \
|
||
-k testdaten/Lahnstein.de-orig.korrigiert.txt -g krone-schmalz.glossar.txt
|
||
# → testdaten/Lahnstein.de-orig.glossar.entwurf.txt
|
||
```
|
||
|
||
| Option | Bedeutung |
|
||
|---|---|
|
||
| `-i`, `--input` | Rohabschrift (`.txt` oder `.vtt`), Pflicht |
|
||
| `-k`, `--korrigiert` | korrigierter Text eines Laufs (optional) |
|
||
| `-g`, `--glossar` | vorhandene Glossare, mehrfach möglich |
|
||
| `-o`, `--output` | Entwurf; Standard: `<Eingabe ohne Endung>.glossar.entwurf.txt` |
|
||
| `-m`, `--model` | Ollama-Modell; Standard: `qwen3:32b` |
|
||
|
||
Der Entwurf ist ein Ausgangspunkt, kein fertiges Glossar: jeden Eintrag prüfen,
|
||
Brauchbares ins Glossar des Beitrags (oder ins gemeinsame) übernehmen. Die Modellantworten
|
||
werden in `<Entwurf>.parts/` zwischengespeichert.
|
||
|
||
## Testdaten
|
||
|
||
`testdaten/` enthält drei Reden, jeweils als automatische YouTube-Untertitel
|
||
(`.vtt`, Original) und daraus mit `vtt2txt.py` gewonnener Rohtext (`.txt`, Eingabe
|
||
für das Programm):
|
||
|
||
- `ramstein.de.*`: Rede über die US Air Base Ramstein (Glossar `ramstein.glossar.txt`)
|
||
- `magdeburg.de-orig.*`: Rede in Magdeburg (Glossar `magdeburg.glossar.txt`)
|
||
- `Lahnstein.de-orig.*`: Rede „Respekt geht anders“ in Lahnstein (Glossar
|
||
`lahnstein.glossar.txt`, erstellt mit Hilfe von `glossar_vorschlag.py`)
|
||
|
||
Alle drei mit dem gemeinsamen `krone-schmalz.glossar.txt` davor.
|
||
|
||
```bash
|
||
./vtt2txt.py -i "testdaten/*.vtt" # erzeugt alle .txt neu
|
||
./abschrift_korrigieren.py -i testdaten/ramstein.de.txt -g krone-schmalz.glossar.txt -g ramstein.glossar.txt
|
||
# → testdaten/ramstein.de.korrigiert.txt (nicht versioniert)
|
||
```
|
||
|
||
Ein Lauf dauert mit `qwen3:32b` vollständig auf einer RTX 3090 etwa 6 Minuten.
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
python3 -m pytest
|
||
```
|
||
|
||
Die Tests (291) brauchen weder Ollama noch Netz: Modellaufrufe und `yt-dlp` werden
|
||
durch Fakes ersetzt. `tests/test_abschrift_korrigieren.py` prüft Abschnittsbildung,
|
||
Satzgrenzen (Abkürzungen und Ordnungszahlen sind kein Satzende), Glossar (inkl.
|
||
„jede Ersetzung im Glossar einer Rede trifft diese Rede, jede im gemeinsamen Glossar
|
||
trifft mindestens eine“), Streaming-Auswertung, Absatzgliederung (Text bleibt
|
||
unverändert, 3–10 Sätze), Verbinden kurzer Absätze (Modell wird nur gefragt, wenn
|
||
beide Nachbarn passen), Zwischenspeicher/Fortsetzen, Zeitauswahl, Sprachen und die
|
||
Kommandozeile.
|
||
`tests/test_glossar_vorschlag.py` prüft Filter (nur Funde im Text, keine bekannten),
|
||
Ausgabeformat (gültiges Glossar, Unsicheres auskommentiert) und Zwischenspeicher.
|
||
`tests/test_aenderungen.py` prüft den Wortvergleich (Satzzeichen, Groß-/Kleinschreibung
|
||
und „z. B.“ zählen nicht), die Liste „Zu prüfen“ (Gründe, Ausnahmen, Glossar,
|
||
fehlende Wortliste) und den HTML-Bericht (vollständiger Text, Absätze, Escaping).
|
||
Die Tests nutzen eine kleine eigene Wortliste statt der des Systems.
|
||
`tests/test_extract_text.py` prüft den Gesamtablauf URL → Text, Sprachwahl
|
||
(Original-Audio, manuell vor automatisch) und Fehlerfälle.
|
||
`tests/test_yt_untertitel.py` prüft Spurauswahl, Aufruf von `yt-dlp` und
|
||
Fehlerfälle (ohne Netz, `yt-dlp` wird durch Fakes ersetzt).
|
||
`tests/test_vtt2txt.py` prüft die VTT-Umwandlung: jede `.vtt` in `testdaten/`
|
||
ergibt genau die zugehörige `.txt`, echte Wiederholungen bleiben erhalten, auch bei
|
||
Untertiteln ohne rollendes Fenster, dazu Zeitauswahl, Muster, Ausgabeverzeichnis und
|
||
Fehlerfälle.
|
||
|
||
## Zwischenstände
|
||
|
||
Jeder fertige Abschnitt, jede Absatzgliederung und jede Entscheidung beim
|
||
Verbinden kurzer Absätze wird in `<Ausgabe>.parts/` gespeichert. Ein erneuter
|
||
Lauf überspringt alles, was schon berechnet ist.
|
||
Ändern sich Modell (auch per `-m`), Prompt, Glossar oder Text eines Abschnitts, wird
|
||
genau dieser Teil neu berechnet. Glossar-Begriffe stehen im Prompt; ein neuer Begriff
|
||
lässt deshalb alle Abschnitte neu korrigieren. Den Ordner kann man jederzeit löschen.
|
||
`glossar_vorschlag.py` speichert seine Modellantworten ebenso in
|
||
`<Entwurf>.parts/`.
|
||
|
||
## Einstellungen
|
||
|
||
Oben in `abschrift_korrigieren.py`:
|
||
|
||
| Konstante | Standard | Bedeutung |
|
||
|---|---|---|
|
||
| `MODEL` | `qwen3:32b` | Ollama-Modell (Standard für `-m`) |
|
||
| `CHUNK_SIZE` | 1200 | Wörter pro Korrekturabschnitt |
|
||
| `MAX_EXTRA_WORDS` | 300 | so weit darf ein Abschnitt bis zum nächsten Satzende wachsen |
|
||
| `MAX_WORD_DEVIATION` | 0.05 | Warnschwelle Wortzahl |
|
||
| `PARAGRAPH_WINDOW` | 150 | Sätze pro Anfrage bei der Absatzgliederung |
|
||
| `MAX_PARAGRAPH_SENTENCES` | 10 | längster erlaubter Absatz |
|
||
| `MIN_PARAGRAPH_SENTENCES` | 3 | kürzester gewünschter Absatz |
|
||
| `NO_SENTENCE_END` | Muster | Abkürzungen, die kein Satzende sind |
|
||
|
||
In `aenderungen.py`:
|
||
|
||
| Konstante | Standard | Bedeutung |
|
||
|---|---|---|
|
||
| `WORDLISTS` | `/usr/share/dict/ngerman`, `…/american-english` | Wortlisten je Sprache |
|
||
| `MIN_SIMILARITY` | 0.6 | darunter gilt eine Ersetzung als „stark verändert“ |
|
||
| `FILLERS` | je Sprache: äh, ähm, …; uh, um, er, … | Füllwörter, deren Streichung nicht gemeldet wird |
|
||
|
||
In `vtt2txt.py`:
|
||
|
||
| Konstante | Standard | Bedeutung |
|
||
|---|---|---|
|
||
| `MAX_SNAP_SECONDS` | 30 | so weit darf ein Zeitbereich bis zur nächsten Satzgrenze wachsen |
|
||
| `ROLLING_SHARE` | 0.5 | ab diesem Anteil sich überlappender Blöcke gilt eine Datei als rollend |
|
||
|
||
## Bekannte Grenzen
|
||
|
||
- Das Modell ändert gelegentlich Wörter eigenmächtig, auch sinnverändernd
|
||
(Magdeburg-Rede ohne Glossar: „Professor“ → „Professorin“, „gedeiliche“ →
|
||
„geregelte“ statt „gedeihliche“). Die Liste „Zu prüfen“ im Bericht findet die
|
||
meisten solcher Fälle, aber nicht alle: Ein falsch erkanntes Wort, das das Modell
|
||
durch ein ähnliches, aber falsches ersetzt, fällt nicht auf. Gefundene Fälle
|
||
gehören ins Glossar.
|
||
- `qwen3:32b` ohne Denkmodus erkennt Hörfehler ohne Glossar nur teilweise und
|
||
setzt kaum `[prüfen: …]`-Marken. Bekannte Fehler gehören deshalb ins Glossar.
|
||
- Einzelbuchstaben und ein- bis zweistellige Zahlen mit Punkt gelten nie als
|
||
Satzende („Plan B.“, „Es waren 12.“); das beeinflusst nur mögliche Absatzgrenzen,
|
||
nicht den Text.
|