feat: mehrere Zeitbereiche (-z) mit Zeit-Überschriften in einer Datei
- Zeitformat: 00:05:10 (hⓂ️s), 00:20 (h:m = Minute 20), 25 (Minute 25)
- -z "00:05-00:06 00:10-00:12 00:25": A-B, A (bis Ende), -B (ab Anfang)
- jeder Bereich unter einer Überschrift [HH:MM:SS - HH:MM:SS] mit den
tatsächlichen Zeiten; nach der Satz-Erweiterung überlappende Blöcke
werden verschmolzen
- rede_korrigieren.py korrigiert und gliedert jeden Block einzeln; das
Modell sieht die Überschriften nicht
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
faea4bf465
commit
0bf42dee74
1 changed files with 700 additions and 254 deletions
|
|
@ -7,7 +7,10 @@ import shutil
|
|||
import tempfile
|
||||
import textwrap
|
||||
|
||||
from vtt2txt import add_time_options, check_time_range, time_suffix, vtt_to_lines
|
||||
from vtt2txt import (
|
||||
add_time_options, join_segments, resolve_time_ranges, split_segments, time_suffix,
|
||||
vtt_to_text,
|
||||
)
|
||||
from yt_untertitel import (
|
||||
YT_DLP, choose_track, default_name, download, fetch_info, no_track_message,
|
||||
original_language,
|
||||
|
|
@ -28,7 +31,7 @@ def parse_args():
|
|||
parser.add_argument(
|
||||
"-o", "--output", type=Path,
|
||||
help="Ausgabedatei (Standard: <Kanal>_<Titel>_<Datum>.txt, "
|
||||
"mit -s/-e zusätzlich der Zeitraum)",
|
||||
"mit -s/-e/-z zusätzlich die Zeiträume)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f", "--fliesstext", action="store_true",
|
||||
|
|
@ -36,7 +39,7 @@ def parse_args():
|
|||
)
|
||||
add_time_options(parser)
|
||||
args = parser.parse_args()
|
||||
check_time_range(parser, args)
|
||||
resolve_time_ranges(parser, args)
|
||||
|
||||
if args.output is not None and args.output.is_dir():
|
||||
parser.error(f"-o/--output ist ein Verzeichnis: {args.output}")
|
||||
|
|
@ -45,12 +48,12 @@ def parse_args():
|
|||
return args
|
||||
|
||||
|
||||
def fetch_text(url, lang=None, start=None, end=None, exact=False):
|
||||
"""Download the subtitles of a video; return (video info, text lines).
|
||||
def fetch_text(url, lang=None, ranges=(), exact=False):
|
||||
"""Download the subtitles of a video; return (video info, text).
|
||||
|
||||
Without `lang` the language of the original audio is used. `start`/`end`
|
||||
(seconds) keep only the captions of that time range, completed to whole
|
||||
sentences unless `exact`.
|
||||
Without `lang` the language of the original audio is used. With time
|
||||
`ranges` (seconds) the text has one block per range under a header with
|
||||
its actual times, see vtt2txt.vtt_to_text.
|
||||
"""
|
||||
info = fetch_info(url)
|
||||
print(f"Video: {info.get('title', '?')}")
|
||||
|
|
@ -70,35 +73,40 @@ def fetch_text(url, lang=None, start=None, end=None, exact=False):
|
|||
vtt = Path(f"{target_base}.{track}.vtt")
|
||||
if not vtt.is_file():
|
||||
raise SystemExit("yt-dlp hat keine Untertitel-Datei geschrieben.")
|
||||
lines = vtt_to_lines(vtt.read_text(encoding="utf-8-sig"), start, end, exact)
|
||||
text = vtt_to_text(vtt.read_text(encoding="utf-8-sig"), ranges, exact)
|
||||
|
||||
if not lines:
|
||||
if start is None and end is None:
|
||||
raise SystemExit("Die Untertitel enthalten keinen Text.")
|
||||
raise SystemExit("Die Untertitel enthalten im gewählten Zeitraum keinen Text.")
|
||||
return info, lines
|
||||
if not text:
|
||||
if ranges:
|
||||
raise SystemExit("Die Untertitel enthalten im gewählten Zeitraum keinen Text.")
|
||||
raise SystemExit("Die Untertitel enthalten keinen Text.")
|
||||
return info, text
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
info, lines = fetch_text(args.url, args.lang, args.start, args.ende, args.exakt)
|
||||
suffix = time_suffix(args.start, args.ende)
|
||||
info, text = fetch_text(args.url, args.lang, args.bereiche, args.exakt)
|
||||
suffix = time_suffix(args.bereiche)
|
||||
output = args.output or Path(f"{default_name(info)}{suffix}.txt")
|
||||
segments = split_segments(text)
|
||||
|
||||
if args.fliesstext:
|
||||
# Never split words, not even at hyphens ("EU-Gipfel")
|
||||
text = textwrap.fill(
|
||||
" ".join(lines), width=FLOW_WIDTH,
|
||||
break_long_words=False, break_on_hyphens=False,
|
||||
)
|
||||
else:
|
||||
text = "\n".join(lines)
|
||||
# Each block on its own, headers stay lines of their own; never split
|
||||
# words, not even at hyphens ("EU-Gipfel")
|
||||
segments = [
|
||||
(header, textwrap.fill(
|
||||
" ".join(body.split()), width=FLOW_WIDTH,
|
||||
break_long_words=False, break_on_hyphens=False,
|
||||
))
|
||||
for header, body in segments
|
||||
]
|
||||
text = join_segments(segments)
|
||||
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(text + "\n", encoding="utf-8")
|
||||
print(
|
||||
f"Gespeichert: {output} "
|
||||
f"({len(text.splitlines())} Zeilen, {len(text.split())} Wörter)"
|
||||
f"({len(text.splitlines())} Zeilen, "
|
||||
f"{sum(len(body.split()) for _, body in segments)} Wörter)"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue