diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f0aced --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Raw audio files are not tracked — only their transcripts (text) are. +**/audio/ +*.mp3 +*.wav +*.m4a +*.ogg +*.flac diff --git a/A1.1/_template.md b/A1.1/_template.md index 3ccf21d..61e7523 100644 --- a/A1.1/_template.md +++ b/A1.1/_template.md @@ -22,6 +22,16 @@ Source: [ ] Course book [ ] Workbook - e.g. fill-in-the-blank conjugation, matching, listening comprehension +## Audio / Listening + +Transcripts are ASR-generated (faster-whisper) unless noted otherwise — +review for errors, especially names/numbers. + +### Track [X.X] — [short description, e.g. "Anna and Ben introduce themselves"] +``` +[transcript text or path to transcripts/lektion-XX/trackname.txt] +``` + ## Notes / gotchas - Anything tricky, false friends, exceptions worth flagging in future practice diff --git a/README.md b/README.md index ba1db9b..14b0f49 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,25 @@ A1.2/ ← added once I move on to the next book 4. Update `INDEX.md` with a one-line entry for the lesson. 5. Commit. +## Audio (listening exercises) + +Course book audio has no printed transcript (the workbook's back section is +the *Lösungsschlüssel* — answer key — not a transcript, so that doesn't help +here). Raw audio files are **not** stored in this repo (see `.gitignore`); +only their transcripts are, since that's what gives Claude context. + +Workflow: +1. Get the audio files locally (publisher CD/app/download), any folder outside git. +2. Run `scripts/transcribe.py` (uses `faster-whisper` on GPU) to transcribe them: + ```bash + pip install faster-whisper + python scripts/transcribe.py "" "A1.1/course-book/transcripts/lektion-XX" + ``` +3. Skim the `.txt` output for ASR mistakes (names, numbers, fast speech) and fix them. +4. Reference/paste the transcript into the lesson's `.md` file under "Audio / Listening", + or just point at the `transcripts/lektion-XX/*.txt` path — either works, transcripts + are small text files so committing them is fine. + ## Adding a new book (e.g. A1.2) Copy the `A1.1/` folder structure (minus content) into a new `A1.2/` folder diff --git a/scripts/transcribe.py b/scripts/transcribe.py new file mode 100644 index 0000000..52f35e6 --- /dev/null +++ b/scripts/transcribe.py @@ -0,0 +1,66 @@ +""" +Transcribe course book listening-exercise audio to German text using +faster-whisper (GPU-accelerated via CTranslate2). + +Setup (one time): + pip install faster-whisper + +Usage: + python transcribe.py + +Example: + python transcribe.py "D:/MenschenA1.1/audio/Lektion01" "../A1.1/course-book/transcripts/lektion-01" + +Each audio file gets a matching .txt file with the same basename. +Review the output — ASR on textbook dialogue audio is generally good but can +mangle names, numbers, and fast/overlapping speech. Correct those before +committing. +""" + +import sys +from pathlib import Path + +from faster_whisper import WhisperModel + +# "large-v3" is most accurate; drop to "medium" if VRAM is limited (~5GB vs ~10GB). +MODEL_SIZE = "large-v3" +DEVICE = "cuda" +COMPUTE_TYPE = "float16" # use "int8_float16" if you run out of VRAM + +AUDIO_EXTENSIONS = {".mp3", ".wav", ".m4a", ".ogg", ".flac"} + + +def main() -> None: + if len(sys.argv) != 3: + print(__doc__) + sys.exit(1) + + audio_dir = Path(sys.argv[1]) + out_dir = Path(sys.argv[2]) + out_dir.mkdir(parents=True, exist_ok=True) + + model = WhisperModel(MODEL_SIZE, device=DEVICE, compute_type=COMPUTE_TYPE) + + audio_files = sorted( + p for p in audio_dir.iterdir() if p.suffix.lower() in AUDIO_EXTENSIONS + ) + if not audio_files: + print(f"No audio files found in {audio_dir}") + return + + for audio_path in audio_files: + print(f"Transcribing {audio_path.name} ...") + segments, info = model.transcribe(str(audio_path), language="de", beam_size=5) + + out_path = out_dir / (audio_path.stem + ".txt") + with out_path.open("w", encoding="utf-8") as f: + for segment in segments: + f.write(f"[{segment.start:6.1f}s] {segment.text.strip()}\n") + + print(f" -> {out_path}") + + print("\nDone. Review transcripts for ASR errors before committing.") + + +if __name__ == "__main__": + main()