feat: Arbeitsordner je Video, Quellenangabe und automatisches Video-Glossar

- Ohne -o/-d schreiben yt_untertitel.py, extract_text.py und
  abschrift_korrigieren.py -u in abschriften/<Name>/ (ein Ordner je
  Video, auch für alle Ausschnitte).
- Beim Download entsteht neben dem Rohtext <Rohtext>.quelle.json mit
  Titel, Kanal, Datum und URL, für den Quellenkopf des fertigen Textes.
- Ein Glossar, das wie sein Ordner heißt (abschriften/<Name>/<Name>.glossar.txt),
  nutzen abschrift_korrigieren.py, glossar_vorschlag.py und aenderungen.py
  automatisch, nach den mit -g angegebenen gemeinsamen Glossaren.
- .gitignore: abschriften/, fertig/, *.quelle.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-11 23:24:32 +02:00
commit 3aa04fff4b

View file

@ -21,7 +21,9 @@ from vtt2txt import (
add_time_options, join_segments, range_argv, resolve_time_ranges, split_segments,
time_suffix, vtt_to_text,
)
from yt_untertitel import YT_DLP, base_language, default_name, original_language
from yt_untertitel import (
YT_DLP, base_language, default_name, original_language, work_folder, write_source,
)
MODEL = "qwen3:32b"
OLLAMA_URL = "http://127.0.0.1:11434/api/chat"
@ -264,6 +266,22 @@ def read_glossary(path):
return terms, replacements
def video_glossary(folder):
"""Glossary of the video in `folder`, named like the folder:
abschriften/<Name>/<Name>.glossar.txt; None if there is none."""
path = folder / f"{folder.resolve().name}.glossar.txt"
return path if path.is_file() else None
def with_video_glossary(glossaries, folder):
"""The given glossaries plus the video glossary of `folder` (last, after the
shared ones), unless it was given; returns (glossaries, added or None)."""
found = video_glossary(folder)
if found is None or any(g.resolve() == found.resolve() for g in glossaries):
return list(glossaries), None
return [*glossaries, found], found
def apply_replacements(text, replacements):
"""Replace whole words in file order; report rules without any match."""
unmatched = []
@ -633,8 +651,8 @@ def parse_args():
parser.add_argument(
"-o", "--output", type=Path,
help="Ausgabedatei (Standard: <Eingabe ohne Endung>.korrigiert.txt, "
"mit -u <Kanal>_<Titel>_<Datum>.korrigiert.txt; mit -z zusätzlich "
"die Bereiche)",
"mit -u abschriften/<Name>/<Name>.korrigiert.txt mit "
"<Name> = <Kanal>_<Titel>_<Datum>; mit -z zusätzlich die Bereiche)",
)
parser.add_argument(
"-g", "--glossar", type=Path, action="append", default=[],
@ -709,9 +727,12 @@ def read_source(args):
f"Der Beitrag ist auf „{lang}“; unterstützt: {', '.join(PROMPTS)} (-l)."
)
suffix = time_suffix(args.bereiche)
target = args.output or Path(f"{default_name(info)}{suffix}.korrigiert.txt")
name = default_name(info)
target = args.output or work_folder(name) / f"{name}{suffix}.korrigiert.txt"
raw = text + "\n"
save_raw_text(raw_text_path(target), raw)
raw_path = raw_text_path(target)
save_raw_text(raw_path, raw)
write_source(info, raw_path.with_suffix(".quelle.json"))
return raw, target, lang
raw = args.input.read_text(encoding="utf-8-sig")
@ -762,10 +783,12 @@ def main():
parts_dir = target.with_suffix(".parts")
terms, replacements = [], []
for glossary in args.glossar:
glossaries, automatic = with_video_glossary(args.glossar, target.parent)
for glossary in glossaries:
more_terms, more_replacements = read_glossary(glossary)
note = " (Video-Glossar, automatisch)" if glossary == automatic else ""
print(
f"Glossar {glossary}: {len(more_terms)} Begriffe, "
f"Glossar {glossary}{note}: {len(more_terms)} Begriffe, "
f"{len(more_replacements)} feste Ersetzungen"
)
terms += more_terms