refactor: Zeitauswahl nur noch über -z, -s/-e entfallen

-z 00:05:10 (ab), -z 0-00:20 (vom Anfang bis), -z 00:05-00:06 und mehrere
Bereiche, mit oder ohne Anführungszeichen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-11 20:20:54 +02:00
commit 8edef11c78
7 changed files with 93 additions and 108 deletions

View file

@ -68,8 +68,8 @@ Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei
# → 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 -s 0:03:17 -e 0:05:25 # nur ein Ausschnitt
./extract_text.py -u URL -z "00:05-00:06 00:10-00:12 00:25" # mehrere Bereiche
./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
./rede_korrigieren.py -i rede.txt # → rede.korrigiert.txt
```
@ -78,7 +78,7 @@ Zwischenschritt im temporären Verzeichnis. Die Spurwahl ist dieselbe wie bei
| `-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 |
| `-s`, `--start` / `-e`, `--ende` / `-z`, `--zeitraeume` | nur Untertitel in diesen Zeiträumen, siehe [Zeitauswahl](#zeitauswahl--s--e--z) |
| `-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 `rede_korrigieren.py` |
@ -95,8 +95,8 @@ Ohne `-o` bzw. `-n` bilden `extract_text.py`, `yt_untertitel.py` und
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 `-s`/`-e`/`-z` kommen die Zeiträume dazu, z. B.
`…_2026-07-30_00-03-17_bis_00-05-25` (nur `-s`: `_ab_…`, nur `-e`: `_bis_…`;
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`: `_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).
@ -147,20 +147,18 @@ zweimal, bleibt sie zweimal erhalten.
./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 -s 0:03:17 -e 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
./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 `-s`/`-e`/`-z` zusätzlich die Zeiträume im Namen |
| `-s`, `--start` | nur Untertitel ab dieser Zeit |
| `-e`, `--ende` | nur Untertitel vor dieser Zeit |
| `-z`, `--zeitraeume` | mehrere Bereiche statt `-s`/`-e` |
| `-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 (`-s`, `-e`, `-z`)
#### Zeitauswahl (`-z`)
Gilt gleich für `vtt2txt.py`, `extract_text.py` und `rede_korrigieren.py`.
@ -172,14 +170,16 @@ Gilt gleich für `vtt2txt.py`, `extract_text.py` und `rede_korrigieren.py`.
| Aufruf | Text |
|---|---|
| `-s 00:05:10` | ab 5:10 bis zum Ende des Videos |
| `-e 00:20` | vom Anfang bis Minute 20 |
| `-s 00:05 -e 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 |
| `-z 00:05:10` | ab 5:10 bis zum Ende des Videos |
| `-z 0-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 |
Bei `-z` werden die Bereiche durch Leerzeichen getrennt: `A-B`, `A` (ab A bis
zum Ende) oder `-B` (vom Anfang bis B). Sie dürfen sich nicht überschneiden und
werden nach der Zeit sortiert.
Ein Bereich ist `A-B`, `A` (ab A bis zum Ende) oder `0-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. `-B` geht nicht, weil
die Kommandozeile es für eine Option hält.
Alle Bereiche landen in **einer** Datei. Vor jedem Bereich steht eine Überschrift
mit seinen tatsächlichen Zeiten:
@ -217,8 +217,8 @@ werden trotzdem umgewandelt; der Exit-Code ist dann 1.
./rede_korrigieren.py -i rede.de.vtt # VTT direkt → rede.de.korrigiert.txt
./rede_korrigieren.py -u URL # YouTube → <Kanal>_<Titel>_<Datum>.korrigiert.txt
./rede_korrigieren.py -i speech.txt -l en # englische Rede
./rede_korrigieren.py -u URL -s 0:03:17 -e 0:05:25 # nur ein Ausschnitt des Videos
./rede_korrigieren.py -u URL -z "00:05-00:06 00:25" # mehrere Bereiche, eine Datei
./rede_korrigieren.py -u URL -z 0:03:17-0:05:25 # nur ein Ausschnitt des Videos
./rede_korrigieren.py -u URL -z 00:05-00:06 00:25 # mehrere Bereiche, eine Datei
```
| Option | Bedeutung |
@ -226,8 +226,8 @@ werden trotzdem umgewandelt; der Exit-Code ist dann 1.
| `-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 der Rede: `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 `-s`/`-e`/`-z` zusätzlich die Zeiträume im Namen |
| `-s`, `--start` / `-e`, `--ende` / `-z`, `--zeitraeume` | nur diese Zeiträume korrigieren, siehe [Zeitauswahl](#zeitauswahl--s--e--z); nur mit `.vtt`-Eingabe oder `-u`, da reiner Text keine Zeitstempel hat |
| `-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 |
Mit Zeitauswahl wird jeder Bereich als eigener Block korrigiert und gegliedert;
@ -330,7 +330,7 @@ falsche feste Ersetzung verdirbt den Text unbemerkt. Zum Übernehmen `# ? ` entf
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--s--e--z)) werden aus
(`[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

View file

@ -31,7 +31,7 @@ def parse_args():
parser.add_argument(
"-o", "--output", type=Path,
help="Ausgabedatei (Standard: <Kanal>_<Titel>_<Datum>.txt, "
"mit -s/-e/-z zusätzlich die Zeiträume)",
"mit -z zusätzlich die Bereiche)",
)
parser.add_argument(
"-f", "--fliesstext", action="store_true",

View file

@ -493,8 +493,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 -s/-e zusätzlich "
"der Zeitraum)",
"mit -u <Kanal>_<Titel>_<Datum>.korrigiert.txt; mit -z zusätzlich "
"die Bereiche)",
)
parser.add_argument(
"-g", "--glossar", type=Path, action="append", default=[],
@ -528,7 +528,7 @@ def parse_args():
if not args.input.is_file():
parser.error(f"Eingabedatei nicht gefunden: {args.input}")
if args.bereiche and args.input.suffix.lower() != ".vtt":
parser.error("-s/-e/-z brauchen Zeitstempel: nur mit .vtt-Eingabe oder -u.")
parser.error("-z braucht Zeitstempel: nur mit .vtt-Eingabe oder -u.")
if args.output is None:
suffix = time_suffix(args.bereiche)
args.output = args.input.with_name(f"{args.input.stem}{suffix}.korrigiert.txt")

View file

@ -130,7 +130,7 @@ def test_cli_errors(tmp_path, monkeypatch, capsys, args, message):
def test_time_range_default_name(fake_yt_dlp, tmp_path, monkeypatch):
monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(GERMAN_VIDEO, vtt=VTT))
monkeypatch.chdir(tmp_path)
run_main(monkeypatch, "-u", URL, "-s", "0:00:02", "-e", "0:00:03")
run_main(monkeypatch, "-u", URL, "-z", "0:00:02-0:00:03")
name = "Kanal_Eine_Rede_2026-03-28_00-00-02_bis_00-00-03.txt"
assert (tmp_path / name).read_text(encoding="utf-8") == (
@ -141,14 +141,14 @@ def test_time_range_default_name(fake_yt_dlp, tmp_path, monkeypatch):
def test_time_range_without_text(fake_yt_dlp, tmp_path, monkeypatch):
monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(GERMAN_VIDEO, vtt=VTT))
with pytest.raises(SystemExit, match="im gewählten Zeitraum keinen Text"):
run_main(monkeypatch, "-u", URL, "-s", "1:00", "-o", tmp_path / "out.txt")
run_main(monkeypatch, "-u", URL, "-z", "1:00", "-o", tmp_path / "out.txt")
def test_time_range_order_error(monkeypatch, capsys):
with pytest.raises(SystemExit) as exc:
run_main(monkeypatch, "-u", URL, "-s", "2:00", "-e", "1:00")
run_main(monkeypatch, "-u", URL, "-z", "2:00-1:00")
assert exc.value.code == 2
assert "muss vor -e/--ende liegen" in capsys.readouterr().err
assert "Start muss vor Ende" in capsys.readouterr().err
CUT_VTT = (
@ -165,7 +165,7 @@ CUT_VTT = (
def test_time_range_sentence_bounds(fake_yt_dlp, tmp_path, monkeypatch, exact, text):
monkeypatch.setattr(yt.subprocess, "run", fake_yt_dlp(GERMAN_VIDEO, vtt=CUT_VTT))
target = tmp_path / "out.txt"
run_main(monkeypatch, "-u", URL, "-s", "0:00:02", "-e", "0:00:03", "-o", target, *exact)
run_main(monkeypatch, "-u", URL, "-z", "0:00:02-0:00:03", "-o", target, *exact)
assert target.read_text(encoding="utf-8") == text
@ -182,7 +182,7 @@ def test_several_ranges_flow_text_keeps_headers(fake_yt_dlp, tmp_path, monkeypat
monkeypatch.chdir(tmp_path)
run_main(monkeypatch, "-u", URL, "-z", "0:00:00-0:00:30 0:02", "-f")
name = "Kanal_Eine_Rede_2026-03-28_00-00-00_bis_00-00-30+ab_00-02-00.txt"
name = "Kanal_Eine_Rede_2026-03-28_bis_00-00-30+ab_00-02-00.txt"
assert (tmp_path / name).read_text(encoding="utf-8") == (
"[00:00:01 - 00:00:02]\nErster Satz der\nRede.\n\n"
"[00:02:00 - 00:02:01]\nDritter Satz, der\netwas länger ist.\n"

View file

@ -523,7 +523,7 @@ def test_cli_errors(sample_copy, monkeypatch, capsys, args, message):
def test_main_url_time_range_default_names(from_url, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
run_main(monkeypatch, "-u", URL, "-s", "0:00:02")
run_main(monkeypatch, "-u", URL, "-z", "0:00:02")
name = "Kanal_Rede_2026-03-28_ab_00-00-02"
raw = (tmp_path / f"{name}.txt").read_text(encoding="utf-8")
@ -534,7 +534,7 @@ def test_main_url_time_range_default_names(from_url, tmp_path, monkeypatch):
def test_main_url_time_range_without_text(from_url, tmp_path, monkeypatch):
with pytest.raises(SystemExit, match="im gewählten Zeitraum keinen Text"):
run_main(monkeypatch, "-u", URL, "-s", "1:00", "-o", tmp_path / "x.korrigiert.txt")
run_main(monkeypatch, "-u", URL, "-z", "1:00", "-o", tmp_path / "x.korrigiert.txt")
def test_main_vtt_time_range(tmp_path, monkeypatch):
@ -542,7 +542,7 @@ def test_main_vtt_time_range(tmp_path, monkeypatch):
vtt.write_text(VTT, encoding="utf-8")
monkeypatch.setattr(rk, "correct_chunk", identity_correct)
monkeypatch.setattr(rk, "find_paragraph_starts", every_nth_start(5))
run_main(monkeypatch, "-i", vtt, "-e", "0:00:02")
run_main(monkeypatch, "-i", vtt, "-z", "0-0:00:02")
result = tmp_path / "rede.de_bis_00-00-02.korrigiert.txt"
assert result.read_text(encoding="utf-8") == (
@ -555,13 +555,13 @@ def test_main_vtt_time_range_without_text(tmp_path, monkeypatch):
vtt.write_text(VTT, encoding="utf-8")
monkeypatch.setattr(rk, "correct_chunk", no_model_calls)
with pytest.raises(SystemExit, match="im gewählten Zeitraum keinen Text"):
run_main(monkeypatch, "-i", vtt, "-s", "1:00")
run_main(monkeypatch, "-i", vtt, "-z", "1:00")
@pytest.mark.parametrize("args, message", [
(["-i", "{sample}", "-s", "1:00"], "nur mit .vtt-Eingabe oder -u"),
(["-i", "{sample}", "-s", "x"], "ungültige Zeit"),
(["-i", "{sample}", "-s", "2:00", "-e", "1:00"], "muss vor -e/--ende liegen"),
(["-i", "{sample}", "-z", "1:00"], "nur mit .vtt-Eingabe oder -u"),
(["-i", "{sample}", "-z", "x"], "ungültiger Bereich"),
(["-i", "{sample}", "-z", "2:00-1:00"], "Start muss vor Ende"),
(["-i", "{sample}", "-z", "0:05 0:10"], "überschneiden"),
])
def test_cli_time_errors(sample_copy, monkeypatch, capsys, args, message):
@ -648,7 +648,7 @@ def test_main_url_excerpt(fake_yt_dlp, yt_dlp_installed, tmp_path, monkeypatch,
monkeypatch.setattr(subprocess, "run", fake_yt_dlp(VIDEO, vtt=CUT_VTT))
systems = recording_model(monkeypatch)
target = tmp_path / "rede.korrigiert.txt"
run_main(monkeypatch, "-u", URL, "-s", "0:00:02", "-e", "0:00:03", "-o", target, *exact)
run_main(monkeypatch, "-u", URL, "-z", "0:00:02-0:00:03", "-o", target, *exact)
assert (tmp_path / "rede.txt").read_text(encoding="utf-8") == raw
assert rk.PROMPTS["de"]["excerpt"] in systems[0]
@ -682,7 +682,7 @@ def test_main_url_several_ranges(fake_yt_dlp, yt_dlp_installed, tmp_path, monkey
monkeypatch.chdir(tmp_path)
run_main(monkeypatch, "-u", URL, "-z", "0:00:00-0:00:30 0:02", "-g", SHARED_GLOSSARY)
name = "Kanal_Rede_2026-03-28_00-00-00_bis_00-00-30+ab_00-02-00"
name = "Kanal_Rede_2026-03-28_bis_00-00-30+ab_00-02-00"
assert (tmp_path / f"{name}.txt").read_text(encoding="utf-8") == (
"[00:00:01 - 00:00:02]\nDer Kremmel ist in Moskau.\n\n"
"[00:02:00 - 00:02:01]\nDas ist bekannt.\n"

View file

@ -189,8 +189,9 @@ def test_parse_cue_time_keeps_minutes_seconds(text, seconds):
@pytest.mark.parametrize("text, ranges", [
("00:5:00-00:6:00 00:10-00:12:00 00:25", [(300, 360), (600, 720), (1500, None)]),
("-00:20", [(None, 1200)]),
("00:25- 00:10-00:12 -00:05", [(None, 300), (600, 720), (1500, None)]),
("0-00:20", [(None, 1200)]),
("0:00:00-0:01", [(None, 60)]),
("00:25- 00:10-00:12 0-00:05", [(None, 300), (600, 720), (1500, None)]),
("00:05-00:06 00:06-00:07", [(300, 360), (360, 420)]),
])
def test_parse_ranges(text, ranges):
@ -199,11 +200,14 @@ def test_parse_ranges(text, ranges):
@pytest.mark.parametrize("text, message", [
("", "keine Bereiche"),
("-", "ungültiger Bereich"),
("-", "vom Anfang: 0-B"),
("-00:20", "vom Anfang: 0-B"),
("0", "das ganze Video"),
("0-", "das ganze Video"),
("00:06-00:05", "Start muss vor Ende"),
("00:05-00:10 00:08-00:12", "überschneiden"),
("00:05 00:10", "überschneiden"),
("-00:05 -00:10", "überschneiden"),
("0-00:05 0-00:10", "überschneiden"),
("00:05-x", "ungültiger Bereich"),
])
def test_parse_ranges_rejects(text, message):
@ -356,7 +360,7 @@ def test_split_segments_inverts_join_segments():
# === Command line ===
def test_main_time_range_default_name(sample_copy, monkeypatch):
run_main(monkeypatch, "-i", sample_copy, "-s", "0:00:30", "-e", "0:01")
run_main(monkeypatch, "-i", sample_copy, "-z", "0:00:30-0:01")
target = sample_copy.with_name("ramstein.de_00-00-30_bis_00-01-00.txt")
full = SAMPLE_TXT.read_text(encoding="utf-8").splitlines()
part = target.read_text(encoding="utf-8").splitlines()
@ -373,19 +377,28 @@ def test_main_several_ranges(sample_copy, monkeypatch):
]
@pytest.mark.parametrize("ranges", [["0:01-0:02 0:05"], ["0:01-0:02", "0:05"]])
def test_main_ranges_with_or_without_quotes(sample_copy, monkeypatch, ranges):
run_main(monkeypatch, "-i", sample_copy, "-z", *ranges)
target = sample_copy.with_name("ramstein.de_00-01-00_bis_00-02-00+ab_00-05-00.txt")
assert target.is_file()
def test_main_time_range_without_captions(sample_copy, monkeypatch, capsys):
with pytest.raises(SystemExit) as exc:
run_main(monkeypatch, "-i", sample_copy, "-s", "99:00:00")
run_main(monkeypatch, "-i", sample_copy, "-z", "99:00:00")
assert exc.value.code == 1
assert "im gewählten Zeitraum" in capsys.readouterr().err
@pytest.mark.parametrize("args, message", [
(["-s", "abc"], "ungültige Zeit"),
(["-s", "1:00", "-e", "0:30"], "muss vor -e/--ende liegen"),
(["-z", "0:05-0:04"], "Start muss vor Ende"),
(["-z", "abc"], "ungültiger Bereich"),
(["-z", "1:00-0:30"], "Start muss vor Ende"),
(["-z", "0:05 0:10"], "überschneiden"),
(["-z", "0:05", "-s", "0:01"], "nicht zusammen mit -s/-e"),
(["-z", "0:05", "0:10"], "überschneiden"),
(["-z=-0:20"], "vom Anfang: 0-B"),
(["-z", "0"], "das ganze Video"),
(["-s", "0:01"], "unrecognized arguments: -s"),
])
def test_cli_time_errors(sample_copy, monkeypatch, capsys, args, message):
with pytest.raises(SystemExit) as exc:
@ -397,10 +410,10 @@ def test_cli_time_errors(sample_copy, monkeypatch, capsys, args, message):
def test_main_exact_option(tmp_path, monkeypatch):
source = tmp_path / "rede.vtt"
source.write_text(SPEECH, encoding="utf-8")
run_main(monkeypatch, "-i", source, "-s", "0:01:46", "-e", "0:01:50", "--exakt")
run_main(monkeypatch, "-i", source, "-z", "0:01:46-0:01:50", "--exakt")
target = tmp_path / "rede_00-01-46_bis_00-01-50.txt"
assert target.read_text(encoding="utf-8") == (
"[00:01:46 - 00:01:49]\nkommt der dritte\nund endet. Der vierte\n"
)
run_main(monkeypatch, "-i", source, "-s", "0:01:46", "-e", "0:01:50")
run_main(monkeypatch, "-i", source, "-z", "0:01:46-0:01:50")
assert target.read_text(encoding="utf-8").endswith("und endet auch.\n")

View file

@ -45,33 +45,22 @@ def parse_user_time(text):
return int(first) * 3600 + int(minutes) * 60 + seconds
def time_option(text):
"""argparse type for -s/--start and -e/--ende."""
try:
return parse_user_time(text)
except ValueError:
raise argparse.ArgumentTypeError(
f"ungültige Zeit „{text}“ ({TIME_EXAMPLES})"
) from None
def parse_ranges(text):
"""Sorted (start, end) seconds from "00:05:00-00:06:00 00:10-00:12 00:25".
"A" or "A-" runs to the end, "-B" from the beginning; None marks an open side.
"A" or "A-" runs to the end, "0-B" from the beginning; None marks an open side.
"""
ranges = []
for item in text.split():
start, dash, end = item.partition("-")
if item.startswith("-"):
raise ValueError(f"ungültiger Bereich „{item}“, vom Anfang: 0-B schreiben")
start, _, end = item.partition("-")
try:
span = (
parse_user_time(start) if start else None,
parse_user_time(end) if end else None,
)
span = (parse_user_time(start) or None, parse_user_time(end) if end else None)
except ValueError:
raise ValueError(f"ungültiger Bereich „{item}“") from None
if span == (None, None):
raise ValueError(f"ungültiger Bereich „{item}“")
raise ValueError(f"Bereich „{item}“ ist das ganze Video: -z weglassen")
if None not in span and span[0] >= span[1]:
raise ValueError(f"Bereich „{item}“: Start muss vor Ende liegen")
ranges.append(span)
@ -86,17 +75,6 @@ def parse_ranges(text):
return ranges
def ranges_option(text):
"""argparse type for -z/--zeitraeume."""
try:
return parse_ranges(text)
except ValueError as error:
raise argparse.ArgumentTypeError(
f"{error} (Bereiche durch Leerzeichen getrennt, je A-B, A = ab A, "
f"-B = bis B; Zeiten {TIME_EXAMPLES})"
) from None
def format_time(seconds, separator=":"):
seconds = int(seconds)
return separator.join(
@ -124,41 +102,35 @@ def time_suffix(ranges):
return "_" + "+".join(range_name(start, end) for start, end in ranges)
RANGE_FORMS = (
"A-B (von A bis B), A (ab A bis zum Ende) oder 0-B (vom Anfang bis B); "
f"Zeiten {TIME_EXAMPLES}"
)
def add_time_options(parser):
parser.add_argument(
"-s", "--start", type=time_option, metavar="ZEIT",
help=f"nur Untertitel ab dieser Zeit, {TIME_EXAMPLES}",
)
parser.add_argument(
"-e", "--ende", type=time_option, metavar="ZEIT",
help="nur Untertitel vor dieser Zeit (Format wie -s)",
)
parser.add_argument(
"-z", "--zeitraeume", type=ranges_option, metavar="BEREICHE",
help="mehrere Bereiche statt -s/-e, durch Leerzeichen getrennt: A-B, "
"A (ab A bis zum Ende) oder -B (vom Anfang bis B), "
"z. B. \"00:05:00-00:06:00 00:10-00:12 00:25\"",
"-z", "--zeitraeume", nargs="+", metavar="BEREICH",
help=f"nur Untertitel in diesen Bereichen: {RANGE_FORMS}; mehrere durch "
"Leerzeichen getrennt, z. B. -z 00:05-00:06 00:10-00:12 00:25",
)
parser.add_argument(
"--exakt", action="store_true",
help="genau an den Zeiten schneiden, statt angeschnittene Sätze vollständig "
f"aufzunehmen (bis {MAX_SNAP_SECONDS} s über den Zeitraum hinaus)",
f"aufzunehmen (bis {MAX_SNAP_SECONDS} s über den Bereich hinaus)",
)
def resolve_time_ranges(parser, args):
"""Check the time options and set args.bereiche: list of (start, end), maybe empty."""
single = args.start is not None or args.ende is not None
if args.zeitraeume is not None:
if single:
parser.error("-z/--zeitraeume nicht zusammen mit -s/-e angeben.")
args.bereiche = args.zeitraeume
elif single:
if args.start is not None and args.ende is not None and args.start >= args.ende:
parser.error("-s/--start muss vor -e/--ende liegen.")
args.bereiche = [(args.start, args.ende)]
else:
"""Check -z and set args.bereiche: sorted list of (start, end), maybe empty."""
if args.zeitraeume is None:
args.bereiche = []
return
# Parts with and without quotes alike: "-z '0:05-0:06 0:25'" or "-z 0:05-0:06 0:25"
try:
args.bereiche = parse_ranges(" ".join(args.zeitraeume))
except ValueError as error:
parser.error(f"-z/--zeitraeume: {error} ({RANGE_FORMS})")
def read_cues(vtt):
@ -423,7 +395,7 @@ def parse_args():
"-o", "--output",
help="Ausgabedatei (nur bei einer Eingabedatei) oder Verzeichnis "
"(vorhanden oder mit / am Ende); Standard: <Eingabe ohne Endung>.txt, "
"mit -s/-e/-z zusätzlich die Zeiträume im Namen",
"mit -z zusätzlich die Bereiche im Namen",
)
add_time_options(parser)
args = parser.parse_args()