Add audio transcription workflow (faster-whisper) and gitignore for raw audio
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# Raw audio files are not tracked — only their transcripts (text) are.
|
||||||
|
**/audio/
|
||||||
|
*.mp3
|
||||||
|
*.wav
|
||||||
|
*.m4a
|
||||||
|
*.ogg
|
||||||
|
*.flac
|
||||||
@@ -22,6 +22,16 @@ Source: [ ] Course book [ ] Workbook
|
|||||||
|
|
||||||
- e.g. fill-in-the-blank conjugation, matching, listening comprehension
|
- 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
|
## Notes / gotchas
|
||||||
|
|
||||||
- Anything tricky, false friends, exceptions worth flagging in future practice
|
- Anything tricky, false friends, exceptions worth flagging in future practice
|
||||||
|
|||||||
@@ -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.
|
4. Update `INDEX.md` with a one-line entry for the lesson.
|
||||||
5. Commit.
|
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 "<path-to-lesson-audio>" "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)
|
## Adding a new book (e.g. A1.2)
|
||||||
|
|
||||||
Copy the `A1.1/` folder structure (minus content) into a new `A1.2/` folder
|
Copy the `A1.1/` folder structure (minus content) into a new `A1.2/` folder
|
||||||
|
|||||||
@@ -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 <path-to-audio-folder> <path-to-output-folder>
|
||||||
|
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user